Stumped on User Defined Functions

Started by mjwillyone, August 25, 2021, 01:05:45 PM

Previous topic - Next topic

mjwillyone

Hello,

I have a question about user defined functions.  I may be going about this all wrong so I could use some help.

I have 1 PDF reports I want to send to 1 person, 1 to another, 2 to another... etc.  I have 5 reports, 1 each to 5 people.  The only difference in these emails is the recipient email address and the PDF report.  To make for less code, I would like to use a function.

What is the least number of lines of code I need to make this happen?

Here is the code I currently have for the function (email accounts and address are not real)

#DefineFunction recipientAdministrator(tolist,attachments)

host="vmm2212.sgvps.net"
fromaddr="jim@jimsanders121.com"
userid="jim@jimsanders121.com"
password="ThisIsMyPW"
port="25"
tolist="IwantMoreEmail@myemail.com"
cclist=""
bcclist=""
subject="Monthly Percentage Report"
mymsg="<html><head><title>Percentage Report</title></head><body><h1>Report Attached</h1>Here is the most recent percentage report!</body></html>"
attachments="S:\Support Percentages\Percentage Report.pdf"
flags="eh"
kInit(host,fromaddr,userid,password,port)
kDest(tolist,cclist,bcclist)
ret = kSendText(subject,mymsg,attachments,flags)
If ret == 0
   errline=kStatusInfo()
   Message("ErrLine",errline)
EndIf
#EndFunction


Now ... how can I run this and insert a different tolist address and a different attachment and do this with all 5 of my recipients? 

I thought that there was a way to pass from outside the function the tolist and attachment info INTO the function, then do this 5 times but I just don't understand the way to do this.

I realize I am a beginner at this .. .and I want to thank anyone who can help.

Mike


JTaylor

You would simply call your function with the desired parameters.
tolist = "jtaylor@email.dom"
attachments = "pdf_file.pdf"

recipientAdministrator(tolist,attachments)

In your function use tolist and attachments as the variables rather than hardcoding the values, as you have done.

Jim

mjwillyone

jTaylor,

Ok .. so just prior to the calling of the function, I make sure that I assign 1 person's email address and file location to the variable....

Then it could look like this?

tolist = "jim@email.com"
attachments = "jim.pdf"
recipientAdministrator(tolist,attachments)

tolist = "doug@email.com"
attachments = "doug.pdf"
recipientAdministrator(tolist,attachments)

tolist = "sandy@email.com"
attachments = "Sandy.pdf"
recipientAdministrator(tolist,attachments)

tolist = "sue@email.com"
attachments = "Sue.pdf"
recipientAdministrator(tolist,attachments)

tolist = "bart@email.com"
attachments = "bart.pdf"
recipientAdministrator(tolist,attachments)

JTaylor

Yep.  Obviously, if you had the data stored in a list or database you could loop and extract rather than duplicating the code multiple times.

Jim

mjwillyone

Quote from: JTaylor on August 25, 2021, 02:20:59 PM
Yep.  Obviously, if you had the data stored in a list or database you could loop and extract rather than duplicating the code multiple times.

Jim

Yes ...  I really should do that.  It's one thing I need to learn to do.  Thank you!

cssyphus

Hi Mike,

One useful tip (if you don't already know it) is how to organize your functions. I learned this from my betters (Stan, Jim, Kirby (and of course Tony)) over the years, and use it all the time myself.

In WinBatch, the functions (UDFs and UDSs) must be defined first, at the top of your script - but this can be a nuisance, since you must scroll past these already completed blocks of code in order to get to the code you are working on. So, we use this structure:


;-------------------------------------------------------------------------------
;dosumting.wbt - runs every Fri by TaskSched
;Purpose: Does [this and this and this] in order to solve [this problem]
;Written: 210822
;Last updated:
; - 210825 - NEW udfDoSummat added because... well, just because
;ToDos:
;Do this and this
;Also add this asap
;-------------------------------------------------------------------------------
gosub udfs

a line of code
a line of code
_name = udfDoSummat('fred')
a line of code
a line of code
EXIT

:udfs ;============================================
    #DEFINEFUNCTION udfDoSummat(_bob) ;-----------------------------------
          if _bob == 'fred' return 'sam'
          else return 'bob'
    #ENDFUNCTION ;udfDoSummat ----------------------------------------------
Return ;udfs ========================================


Did you notice the gosub udfs ?  Simple idea but so helpful... And the top comments... only use what will be helpful to you. Short scripts (50-70 lines) might only need one or two lines up top - but trust me: you really do want to record why you wrote the script. If you're like me, three years from now you'll thank yourself.

Another thing that I do (don't know if it's good practice or not) is to name my variables with an _underscore as the first character - just because it then becomes very easy to search through the code for any specific variable. Suppose you want to name your variable "message" - searching for _message will not locate every time you used the message() command, nor will it find other vars that include "message" in their name (such as _prevMessage, or _nextMessage, or etc.

It is also important (as your code grows larger) to remember the difference between UDFs and UDSs... (again, forgive me if you already know). UDFs are closed systems - they know nothing outside themselves except what you pass into them, and anything done inside them is isolated from teh rest of the code except those values you pass back out.  In other words, any variables you create inside the function disappear when the function is completed - so they don't remain behind to clutter your global namespace (i.e. list of variables used). UDSs work similarly to functions, except that they have full access to everything outside them, and any variables they create remain behind (unless they are explicitly dropped via the drop() command).

One final tip: for longer programs (and I've written many over 2000 lines) modularize your code where possible. A finished program should be easy to read years later:


goSub UDFs

goSub SetupEnv
goSub GetCurrentIP
if _location == 'home'
   goSub homeStuff
else
  goSub workStuff
endif

EXIT

:SetupEnv ;------------------------------------
    ;do a bunch of stuff
Return ;SetupEnv ------------------------------
:homeStuff ;-----------------------------------
    ;do a bunch of stuff
Return ;homeStuff -----------------------------
:workStuff ;-----------------------------------
    ;do a bunch of stuff
Return ;workStuff -----------------------------
:UDFs ;----------------------------------------
    #DEFINEFUNCTION etc etc etc
    #END FUNCTION
Return ;UDFs ----------------------------------


The structure below is also super-useful - it starts a program and keeps it running forever, until something in your code stops it.


_end = timeAdd(timeYmdHms(), `0000:00:00:3:00:00`)
while 1
    goSub doThisThing
    goSub doThatThing
    goSub doAnotherThing
    if timeYmdHms() > _end then terminate(1, `MyApp`, `MyApp just finished after running for 3 hours`)
endwhile

EXIT

:doThisThing
Return ;doThisThing
:doThatThing
etc


And learn to use arrays - they are very helpful.


_arrList = arrayize('jim@email.com|jim.pdf, doug@email.com|doug.pdf, sandy@email.com|sandy.pdf')

for _n = 0 to arrInfo(_arrList, 6)-1  ;-1 b/c arrInfo returns a 1-based number, but arrays are zero-based
    _job = _arrList[_n]
    _arrTmp = arrayize(_job, '|')
    _eml = _arrTmp[0]
    _pdf = _arrTmp[1]
    udfSendEmail(_eml, _pdf)
next

mjwillyone

Wow!  Thanks, cssyphus!   I had not heard about using a gosub to grab the UDF .. great idea!  The other ideas are a real help, too!  I do need to learn to use Arrays ...  especially helpful to me in using text file to read for input!

Thank you very much!!  You spent quite the time on that post and I appreciate it!

Mike

cssyphus

Happy to help... just passing along the stellar assistance that Tony, Stan, Jim, Kirby, IFICANT et al provided to me over the years. It's a great community that Tony has created here.

A couple of other ideas that you may be familiar with or not...

Using F10, F11 and F9 to step-through your code as it executes... invaluable when testing/troubleshooting. Probably you already know about these...

And using DebugTrace() for larger scripts (or where stepping through just doesn't work).  DebugTrace writes each line of the script to a log file as it executes. I have a script that runs continuously in the background watching for events and responding to them (for example, incoming Outlook emails create a pop-up but it frequently pops-up BEHIND other windows... so my script watches for that pop-up window ( winExist() ) and stores the time... after one minute, it displays a WinBatch message() in case I haven't seen the new email alert yet. This has saved my bacon countless times). Anyway, this script now does 200+ things and is over 7,000 lines - can't step through it. So I have debugTrace running continuously... and one of the GoSubs checks the size of the DebugTrace log file and, if it is greater than 50Mb, deletes it. DebugTrace handles that situation with all the elegance we've come to expect from WinBatch and simply re-creates the log starting from that point.  This, too, has saved my bacon during the many times that the script crashes an hour or two after adding a new bit of functionality.  I regularly open this log file and search through it for ERROR SUPPRESSED messages, which indicate that some feature of my script broke, but the script was able to recover and limp along. Without that log file, I might not even know that some feature is not working.

One last thing - and this might be poor coding practice but WinBatch allows it... I often deal with strings, and strings often include apostrophes and quote marks... which break strings. (eg. _msg = 'Don't be late' is a broken string, and so is _msg = "She told me to "Go Home"" ) So, as a matter of habit, I use backticks ( `these things` ) for all strings. Real string data almost never includes backticks, so that problem is solved. However, when switching to other languages, that muscle memory can be fraught. But it's been useful enough that I haven't changed that habit (I can hear Tony cringing through the San Juans...)

If you decide to write a script that runs continuously in the background, you'll want to know about IFICantBYTE's amazing script MegaPop - it makes it easy to add your script to the system tray (actually, WinBatch itself makes that part easy - intControl(1007) ) and interact with it via a pop-up menu. Not sure how to find it these days, but if you want it and can't find it easily post a note and we'll assist.