[Class] biga.ahk (166 utility methods)

Post your working scripts, libraries and tools for AHK v1.1 and older
User avatar
Chunjee
Posts: 1418
Joined: 18 Apr 2014, 19:05
Contact:

[Class] biga.ahk (166 utility methods)

Post by Chunjee » 29 Sep 2019, 10:06

Image

Image Image Image Image Image

A modern immutable utility library for AutoHotKey

Mirrors functionality and method names of Lodash; written in AutoHotkey, with ZERO dependencies.

⚠️alpha - The API might change, and may have defects prior to v1.0.0⚠️

Installation

In a terminal or command line navigated to your project folder:

Code: Select all

npm install biga.ahk

In your code only the file export.ahk needs to be included:

Code: Select all

#Include %A_ScriptDir%\node_modules
#Include biga.ahk\export.ahk
A := new biga()
msgbox, % A.join(["a", "b", "c"], " ")
; => "a b c"
You may also review or copy the package from ./export.ahk on GitHub; #Include it however you would normally when manually downloading a library.


AutoHotkey v1.1.05 or higher is required


API

Initiate an instance of the class and use as needed

Code: Select all

A := new biga()
wordsArray := A.words("This could be real real useful")
; => ["This", "could", "be", "real, "real", "useful"]
msgbox, % wordsArray.Count()
; => 6
uniqWords := A.uniq(wordsArray)
; => ["This", "could", "be", "real", "useful"]
msgbox, % A.join(uniqWords, " ")
; => "This could be real useful"
Longer examples available at https://github.com/biga-ahk/biga.ahk/tree/master/examples

Documentation
Getting Started: https://biga-ahk.github.io/biga.ahk/#getting-started
All working methods documented at https://biga-ahk.github.io/biga.ahk

Contributing

Please make pull requests to source found at https://github.com/biga-ahk/biga.ahk
Last edited by Chunjee on 06 Mar 2024, 10:42, edited 15 times in total.

User avatar
Chunjee
Posts: 1418
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Class] biga.ahk (lodash for ahk)

Post by Chunjee » 29 Sep 2019, 10:08

Attachments
export.ahk
v0.52.0
(67.61 KiB) Downloaded 168 times
export.ahk
v0.51.0
(67.26 KiB) Downloaded 158 times
Last edited by Chunjee on 30 Dec 2022, 15:19, edited 22 times in total.

User avatar
Chunjee
Posts: 1418
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Class] biga.ahk (lodash for ahk)

Post by Chunjee » 29 Sep 2019, 10:14

ahk Considerations
By default A.throwExceptions := true Wherever type errors can be detected, biga will throw an exception pointing out the location the error occurred. Set this to false if you would like your script to continue without being stopped by biga.ahk
By default A.limit := -1 Wherever possible biga.ahk will refer to this concerning limiting actions not specified by method arguments. Set this to 1 to get closer to the javascript experience. Currently only used by .replace ie should A.replace("aaaaaa", "a", "b") return "bbbbbb", "baaaaa", or something in between?


Examples
In the following example, we take on the role of a blogger, who wants to automate some features of their blog

Code: Select all

SetBatchLines -1
#NoTrayIcon
#SingleInstance force
#Include %A_ScriptDir%\node_modules
#Include biga.ahk\export.ahk

A := new biga()

blogPost := "This recipe is for a really scrumptious soup from Thailand. Grab a big bunch of lemongrass. We also need tomatoes."
; let's start by finding some interesting tags for this post. Breakup this post into an array of words and remove any duplicates
allWords := A.words(blogPost)
uniqWords := A.uniq(allWords)

; short words aren't useful for post tags. Let's remove anything that isn't at least 8 characters long
tagShortList := A.filter(uniqWords, Func("filterlengthFunc"))
filterlengthFunc(o) {
    global
    ; We use A.size to measure the length of the string. But it can measure objects too
    ; StrLen would also work fine here and remove the need for global scope in this function
    if (A.size(o) >= 8) {
        return true
    }
}

; the blog software wants all tags lowercase and in one long string separated by ","
lowercaseTags := A.map(tagShortList, A.toLower)
tags := A.join(lowercaseTags, ",")
; => "scrumptious,thailand,lemongrass,tomatoes"

; Let's pretend this blogpost was a lot longer and we only want {{3}} tags for this post. We can choose some tags at random from the larger collection
lessTags := A.sampleSize(lowercaseTags, 3)

; For the main page let's make a preview of the blogpost. 40 characters ought to do
postPreview := A.truncate(blogPost, 40)

; actually I prefer 15 words. You can combine different methods together for more power.
; .split creates an array limited to 15 items and .join turns it back into a string
postPreview := A.join(A.split(blogPost," ",15)," ") " [...]"
; => This recipe is for a really scrumptious soup from Thailand. Grab a big bunch of [...]

ExitApp


In this example, let's take on the role of a gaming script that announces which enemy to attack

Code: Select all

SetBatchLines -1
#NoTrayIcon
#SingleInstance force
#Include %A_ScriptDir%\node_modules
#Include biga.ahk\export.ahk

A := new biga()

enemies := [{"name": "bear", "hp": 200, "armor": 20, "status": "exposed_wound"}
    , {"name": "wolf", "hp": 100, "armor": 12}
    , {"name": "crab", "hp": 150, "armor": 99, "magicweakness": true}]

; Let's sort the enemies by hp and announce the one with the lowest hp as a target
sortedEnemies := A.sortBy(enemies, "hp")
; sorting by hp will move the lowest hp object to index 1
target := sortedEnemies[1].name

newEnemy := {"name": "weakened bear", "hp": 10, "armor": 5}
; a new enemy has appeared after we sorted the array. Instead of resorting; let's practice searching for the correct spot to insert at
mappedHP := A.map(sortedEnemies, "hp")
insertIndex := A.sortedIndex(mappedHP, newEnemy.hp)
; => 1
sortedEnemies.InsertAt(insertIndex, newEnemy)

; Assume rogues get bonus damage to anything with a status of "exposed_{{x}}" let's filter the sorted array by those and call all of them out. In this case there is only 1
exposedTargets := A.filter(sortedEnemies, Func("filterexposedFunc"))
filterexposedFunc(o) {
    global
    ; We use A.startsWith inside this function to check the status
    return % A.startsWith(o.status, "exposed")
}

; We can format our message with StartCase which is a ittle like ahk's TitleCase
callOutMessage := " Everyone attack: " A.startCase(sortedEnemies[1].name) "`n Rogues attack: " A.startCase(exposedTargets[1].name)

; Search the array of monsters for the first instance of one with "magicweakness" = true
magicweaknessTarget := A.find(sortedEnemies,"magicweakness")
callOutMessage := "Mages attack: " magicweaknessTarget.name

ExitApp


For the last example let's administer some online comments.

Code: Select all

SetBatchLines -1
#NoTrayIcon
#SingleInstance force
#Include %A_ScriptDir%\node_modules
#Include biga.ahk\export.ahk

A := new biga()

comments := [{"text": "I think...", "likes": 1, "sentiment": -9, "author": "Bob", "subscriber": false}
    , {"text": "You see...", "likes": 2, "sentiment": 10, "author": "Fred", "subscriber": true}
    , {"text": "Listen....", "likes": 9, "sentiment": 80, "author": "Barney", "subscriber": true}]

Newcomments := [{"text": "You see...", "likes": 2, "sentiment": 10, "author": "Fred", "subscriber": true}
    , {"text": "You see...", "likes": 2, "sentiment": 10, "author": "Fred", "subscriber": true}]

; You can combine two arrays with .concat. 
A.concat(comments, Newcomments)
; biga does NOT mutate inputs, changes are always delivered by the return value/object
comments := A.concat(comments, Newcomments)

; Theres a bug somewhere causing comments to be duplicated. In another example we used .uniq to remove duplicate strings, but it works on whole objects too.
comments := A.uniq(comments)

; Arrays can be reversed easily. Let's ensure that the negative comments are at the end of the array by sorting then reversing
negativeCommentsLast := A.reverse(A.sortBy(comments, "sentiment"))

; We can map the "sentiment" values to a new array for finding the average
sentimentArray := A.map(comments, "sentiment")
; => [-9, 10, 80]

; If you want to find out who is new in the comment section, how about mapping all the author names and finding the difference with regular posters
newFaces := A.difference(A.map(comments,"author"), ["Regular1","Regular2","Fred"])
; => ["Bob","Barney"]

; Let's choose a random subscriber comment to feature. Obviously we could filter by subscribers only and choose, but for the sake of example let's perform some logic
while (!featureComment) {
    comment := A.sample(comments)
    if (A.isMatch(comment, {"subscriber": true})) {
        featureComment := comment
    }
}

; Before injecting the comment into html, we can trim any leading and trailing whitespace
featureCommentText := A.trim(featureComment.text)
; let's trim any puncuation off the end as well
featureCommentText := A.trimEnd(featureCommentText, " .!?;:")

; .replace works like StrReplace but also accepts javascript formatted regular expressions. We'll remove any scripts the comment might have
featureCommentText := A.replace(featureCommentText, "/(<script>[\s\S]+<\/script>)/")

ExitApp
Last edited by Chunjee on 13 May 2020, 22:14, edited 7 times in total.

User avatar
Chunjee
Posts: 1418
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Class] biga.ahk (lodash for ahk)

Post by Chunjee » 29 Sep 2019, 10:49

TestDate: passing
2019-10-02: 138
2019-10-06: 184
2019-10-08: 202
2019-10-13: 215
2019-11-13: 261
2019-11-18: 293
2020-01-28: 343
2020-04-06: 362
2020-07-23: 400
2020-10-28: 439
2020-12-28: 449
2021-01-27: 496
2021-06-06: 588
2021-07-28: 632
2022-05-20: 680
2022-07-23: 698


Q & A

Why?
I like using Lodash, I really like ahk. I wrote this for myself so that I never have to miss my favorite utilities while playing with ahk. Maybe you will find it handy as well. When you get used to it you will spend way less time thinking about loops and more time thinking about your application.

What about _.{{x}}? When is that coming to biga.ahk?
It would be great if you could contribute to the project. But feel free to post your request(s) in this thread.

Cool how do I get started with it?
https://biga-ahk.github.io/biga.ahk/#/getting-started

Is this faster than plain ahk?
No, but also yes.
This is more about improving the time efficiency for the programmer than improving the time efficiency for the machine. It'll be easier and faster for you to code what you want, but it will run a few milliseconds slower than doing everything custom. SetBatchLines -1 is of course recommended.
1 hour cloud compute: $0.021811
1 hour USA programmer: $40+

Why do I have to install this with npm?
You don't. You can copy and paste it from github if you prefer.

Do I need nodeJS installed for this to work? Does this run through a browser?
No. These are all written in ahk. They just have the same method names. There are no outside dependencies.


Plugins
You my now write plugin methods. Please see the following guide: A brief guide to consistant biga.ahk plugin formatting

Here is an example plugin: https://github.com/Chunjee/frequencies_bigaplugin.ahk
Last edited by Chunjee on 24 Jul 2022, 01:39, edited 16 times in total.

User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: [Class] biga.ahk (utilities mirroring Lodash)

Post by kczx3 » 01 Oct 2019, 11:59

Why make the user instantiate the class?

User avatar
Chunjee
Posts: 1418
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Class] biga.ahk (utilities mirroring Lodash)

Post by Chunjee » 01 Oct 2019, 17:40

kczx3 wrote:
01 Oct 2019, 11:59
Why make the user instantiate the class?

So as not to add to the global namespace without the user's knowledge. a one character variable is somewhat unconventional and allows the user to make that kind of decision.
As it is now mirrors other ahk class libraries and the Lodash norm.

Code: Select all

; ahk
ChromeInst := new Chrome("ChromeProfile")

Code: Select all

// javascript
var _ = require('lodash');
In future versions this could be changed by adding A := new biga() to the #include file if that is something the ecosystem wants.

Additionally:
Every library/module should be encapsulated as a class. The only global variable that should be used by that class is the class name.

All code inside your class should be written under the assumption that the class may become a nested class in the future.
Last edited by Chunjee on 15 Oct 2019, 16:44, edited 1 time in total.

burque505
Posts: 1732
Joined: 22 Jan 2017, 19:37

Re: [Class] biga.ahk (utilities mirroring Lodash)

Post by burque505 » 01 Oct 2019, 20:05

Thank you very much for this, @Chunjee.
For my installation, the example script under "Installation" on the "Getting Started" guide page needed to be changed to

Code: Select all

#Include C:\Users\%A_UserName%\node_modules
and then worked.
I look forward a lot to using this. Your hard work on this is certainly appreciated.
Regards,
burque505

User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: [Class] biga.ahk (utilities mirroring Lodash)

Post by kczx3 » 01 Oct 2019, 21:00

Aren’t all the methods static? If so, there’s probably no need to instantiate it.

guest3456
Posts: 3462
Joined: 09 Oct 2013, 10:31

Re: [Class] biga.ahk (utilities mirroring Lodash)

Post by guest3456 » 01 Oct 2019, 21:00

well done. keep instantiation


User avatar
Chunjee
Posts: 1418
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Class] biga.ahk (utilities mirroring Lodash)

Post by Chunjee » 01 Oct 2019, 21:43

burque505 wrote:
01 Oct 2019, 20:05
For my installation, [...] needed to be changed to #Include C:\Users\%A_UserName%\node_modules and then worked.
Ah that is probably the default path in a cmd console. I will update the documentation in the morning to include the instructions to navigate to the project forlder (via cd)

Thanks for the report and feedback.

User avatar
Chunjee
Posts: 1418
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Class] biga.ahk (utilities mirroring Lodash)

Post by Chunjee » 01 Oct 2019, 22:16

kczx3 wrote:
01 Oct 2019, 21:00
Aren’t all the methods static? If so, there’s probably no need to instantiate it.
from my imperfect knowledge plus a few tests, __New() is not called when accessing the class like this, some methods will have unexpected behavior as they depend on attributes set when a new instance is created.

ie.
this.throwExceptions, this.caseSensitive, this.limit will all be ""

After reviewing GeekDude's Classes in AHK, a Dissection I see that class attributes can be set like so:

Code: Select all

static throwExceptions := true
static caseSensitive := false
static limit := -1

So I'll test that as well

User avatar
Chunjee
Posts: 1418
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Class] biga.ahk (utilities mirroring Lodash)

Post by Chunjee » 02 Oct 2019, 13:16

in v0.3.4 the #Include is now structured

Code: Select all

class biga {
    ; class attributes
    static throwExceptions := true
    static caseSensitive := false
    static limit := -1
    ; {{methods}}
}

class A extends biga {
    ; no changes
}
Which means a "superobject" named A will be available at the global scope without creating an instance of biga(). This will continue to exist as a convenience but as a normal practice I would recommend creating an instance of any class you use.

I also added .tail and .head
Last edited by Chunjee on 14 Aug 2021, 12:22, edited 1 time in total.


User avatar
Chunjee
Posts: 1418
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Class] biga.ahk (utilities mirroring Lodash)

Post by Chunjee » 06 Oct 2019, 16:20

v0.6.0 is now on npm

.matches is officially IN. The shorthands for .filter and .find are working and tested. I should be able to get the rest of the shorthands online this week.

Speaking of tests, there are 184 written and all passing :thumbup: :thumbup: :thumbup:

User avatar
Chunjee
Posts: 1418
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Class] biga.ahk (utilities mirroring Lodash)

Post by Chunjee » 07 Oct 2019, 07:35

v0.7.0 adds .intersection and updates .sortBy to work with functions.

User avatar
Chunjee
Posts: 1418
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Class] biga.ahk (utilities mirroring Lodash)

Post by Chunjee » 07 Oct 2019, 11:27

v0.8.0 adds .property (docs) and as a part of that I had to rethink and rewrite .map almost from scratch because it was faking that kind of shorthand before.
Last edited by Chunjee on 13 Oct 2019, 19:19, edited 1 time in total.

User avatar
Chunjee
Posts: 1418
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Class] biga.ahk (utilities mirroring Lodash)

Post by Chunjee » 08 Oct 2019, 09:58

v0.9.0 adds .propertyMatches (docs) v0.9.1 adds some fixes so that it will accept both ["a", "b"] and "a.b" notation.

I had to dig deep to get this done. I've never used this but now that I understand it, I see how it can be useful. I will endeavor to add anther realworld example exploring all the shorthands (which btw are done, at least foundationally)


Filter all users by their privacy settings, which is nested under options object:

Code: Select all

objects := [{ "name": "fred", "options": {"private": true} }
    , { "name": "barney", "options": {"private": false} }
    , { "name": "pebbles", "options": {"private": false} }]

; style "a.b"
A.filter(objects, A.matchesProperty("options.private", false))
; => [{ "name": "barney", "options": {"private": false} }, { "name": "pebbles", "options": {"private": false} }]

; style ["a", "b"]
A.filter(objects, A.matchesProperty(["options", "private"], false))
; => [{ "name": "barney", "options": {"private": false} }, { "name": "pebbles", "options": {"private": false} }]
Last edited by Chunjee on 13 Oct 2019, 19:19, edited 1 time in total.

User avatar
Chunjee
Posts: 1418
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Class] biga.ahk (utilities mirroring Lodash)

Post by Chunjee » 13 Oct 2019, 14:23

v0.10.2 is now on github and npm .every (docs) was the main addition and here is some examples:

Here we have a simple plain array that we check if every value is true in it.

Code: Select all

A := new biga()
votes := [true, false, true, true]
A.every(votes, Func("fn_istrue"))
; => false
fn_istrue(value) {
    if (value != true) {
        return false
    } 
    return true
}

Here we have more complex objects but using the .matchesProperty shorthand of [votes.1, "yes"] we can quickly check if everyone voted "yes" on option 1.

Code: Select all

A := new biga()
userVotes := [{"name":"fred", "votes": ["yes","yes"]}
            , {"name":"bill", "votes": ["no","yes"]}
            , {"name":"jake", "votes": ["no","yes"]}]

A.every(userVotes, ["votes.1", "yes"])
; => false
Hmmm how about if we check the 2nd voting option with [votes.2, "yes"]

Code: Select all

A.every(userVotes, ["votes.2", "yes"])
; => true
:thumbup: :thumbup: :thumbup:

User avatar
Chunjee
Posts: 1418
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Class] biga.ahk (utilities mirroring Lodash)

Post by Chunjee » 15 Oct 2019, 10:50

v0.11.0 brings .partition (docs) and fixes what I would think was a big bug with some methods that can call functions.

Here is an example where we partition those voting users into two arrays, one that voted yes on option.1 and a second array that voted no.

Code: Select all

A := new biga()
userVotes := [{"name":"fred", "votes": ["yes","yes"]}
            , {"name":"bill", "votes": ["no","yes"]}
            , {"name":"jake", "votes": ["no","yes"]}]

A.partition(userVotes, ["votes.1", "yes"])
/* => [{"name":"fred", "votes":[1:"yes", 2:"yes"]}]
, [{"name":"bill", "votes":[1:"no", 2:"yes"]}, {"name":"jake", "votes":[1:"no", 2:"yes"]}]
*/

User avatar
Chunjee
Posts: 1418
Joined: 18 Apr 2014, 19:05
Contact:

Re: [Class] biga.ahk (utilities mirroring Lodash)

Post by Chunjee » 21 Oct 2019, 09:58

v0.12.0 is now available on npm and github. .find and .filter now work with all three shorthands. between the two methods I think three-ish were missing previously.

I removed the test A.every(["", "", ""], A.isUndefined) ; => true because I just couldn't get it working and I got tired of messing with it. If someone could figure out that out it would be a valued addition.
Last edited by Chunjee on 25 Mar 2022, 13:07, edited 1 time in total.

Post Reply

Return to “Scripts and Functions (v1)”