Question about dereferencing

Hello,

I’m making a script that will allow my users to create a cronjob without using the command line. I’m aware that there are probably easier ways to do this, but it’s also an excercise for my own knowledge. I’ve gotten pretty far and am trying to reuse a handler for user input. Here is the relevant code (note I’m only posting one instance, the minutes, for brevity):

set x to "Please set the time you would like the script to run next for:  " & return & return & ""

set minuteTag to "Use a value from 0 to 59 or * for every Minute:  "
set theMinute to 0
set minuteMsg to x & minuteTag
set minuteComp to "((0 > a) or (a > 59))"

on doDisp(a, b, c)
	set a to text returned of (display dialog b default answer "*")
	if text of a is "*" then
	else if a is not "*" then
		set a to a as small integer
		repeat while ((0 > a) or (a > 59))
			log c
			set a to text returned of (display dialog "You have entered a wrong value.  Please try again." & return & return & b default answer "*")
			if text of a is "*" then
				exit repeat
			else if a is not "*" then
				set a to a as small integer
			end if
		end repeat
	end if
	return a
end doDisp

set theMinute to doDisp(theMinute, minuteMsg, minuteComp)

the problem i’m having is that i want to dereference the “((0 > a) or (a >59))” as the variable “c”, but the code won’t run like that. It will run as typed above, all variables are globals as I’d like to use this for an AppleScript Studio project down the road.

Any advice or comments welcome. I’m new here so take it easy.

Hi, waltr.

Your value for c (ie. minuteComp) isn’t a “reference” but a line of text representing some script code.

If you just want to specify minimum and maximum values for a, it would be much simpler to pass these to the handler as individual parameters. But if you need to be be able to supply more flexible checks than that, you could compile the script code “on the fly”, like this:

set x to "Please set the time you would like the script to run next for:  " & return & return & ""

set minuteTag to "Use a value from 0 to 59 or * for every Minute:  "
set theMinute to 0
set minuteMsg to x & minuteTag
set minuteComp to "((0 > a) or (a > 59))"

on doDisp(a, b, c)
	set a to text returned of (display dialog b default answer "*")
	if a is "*" then
	else
		repeat while (run script ("set a to " & a & return & c)) -- compiling on the fly
			set a to text returned of (display dialog "You have entered a wrong value.  Please try again." & return & return & b default answer "*")
			if a is "*" then exit repeat
			set a to a as integer
		end repeat
	end if
	return a
end doDisp

set theMinute to doDisp(theMinute, minuteMsg, minuteComp)

Better still, since it doesn’t require on-the-fly compiling and is therefore faster, would be to supply a check routine in a script object, which would then be passed as your ‘c’ parameter:

set x to "Please set the time you would like the script to run next for:  " & return & return & ""

set minuteTag to "Use a value from 0 to 59 or * for every Minute:  "
set theMinute to 0
set minuteMsg to x & minuteTag

script minuteComp -- script object containing a parameter-check handler
	on badValue(a)
		return ((0 > a) or (a > 59))
	end badValue
end script

on doDisp(a, b, c)
	set a to text returned of (display dialog b default answer "*")
	if a is "*" then
	else
		repeat while (c's badValue(a)) -- using the handler in the passed script object
			set a to text returned of (display dialog "You have entered a wrong value.  Please try again." & return & return & b default answer "*")
			if a is "*" then exit repeat
			set a to a as integer
		end repeat
	end if
	return a
end doDisp

set theMinute to doDisp(theMinute, minuteMsg, minuteComp)

Other parameter checks passed to the handler would be other script objects, each containing a handler called ‘badValue(a)’ that would contain the appropriate code for that check. So for instance, the equivalent hourComp would be:

script hourComp
	on badValue(a)
		return ((0 > a) or (a > 23))
	end badValue
end script

Thanks for the reply, I’ll give this a try!

Thanks again for the help. Here’s the final code if anyone is interested.


set minuteTag to "Use a value from 0 to 59 or * for every Minute:  "
set x to "Please set the time you would like the script to run next for:  " & return & return & ""
script baseComp
	on badValue(a)
		if a is not "*" then
			set myList to {}
			repeat with i from 0 to 70 by 1
				set end of myList to i
			end repeat
			script myInt
				try
					set a to a as integer
				end try
				return (myList does not contain a)
			end script
			run myInt
		end if
	end badValue
end script

set theMinute to 0
set minuteMsg to x & minuteTag
script minuteComp
	on badValue(a)
		if a is not "*" then
			set myList to {}
			repeat with i from 0 to 59 by 1
				set end of myList to i
			end repeat
			script myInt
				try
					set a to a as integer
				end try
				return (myList does not contain a)
			end script
			run myInt
		end if
	end badValue
end script

on doDisp(a, b, c, d)
	set a to 0
	set a to text returned of (display dialog b default answer "*")
	if text of a is not "*" then
		repeat while (c's badValue(a))
			log (c's badValue(a))
			set a to text returned of (display dialog "You have entered a wrong value.  Please try again." & return & return & b default answer "*")
		end repeat
		repeat while (d's badValue(a))
			log (d's badValue(a))
			set a to text returned of (display dialog "You have entered a wrong value.  Please try again." & return & return & b default answer "*")
		end repeat
	end if
	return a
end doDisp

set theMinute to doDisp(theMinute, minuteMsg, baseComp, minuteComp)

Works pretty well, although a user on 10.3 was able to put in garbage data.

Thanks for all the help!

Hi, waltr.

Thanks for the feedback. I’m glad you’ve had fun with the script objects. :slight_smile:

The ones in your script seem a little complicated for what they do. Also, they don’t return anything when ‘a’ is “*”, which they should ” though this doesn’t cause any problems because ‘doDisp()’ doesn’t pass them that value anyway. Here’s a shorter, faster, and slightly more secure version of ‘baseComp’ for your perusal:

script baseComp
	on badValue(a)
		if (a is "*") then return false -- not a bad value
		try
			-- Try coercing to number rather than to integer, in case the string represents a real.
			-- (Since Panther, coercing to integer rounds off reals instead of erroring.)
			set b to a as number
			return ((a is "") or (class of b is real) or (b < 0) or (b > 70))
		on error
			return true
		end try
	end badValue
end script

As I suggested in my previous post, if you just want to check parameters against minimum and maximum permissible values, you could just have one ‘badValue()’ handler and pass it those values:

on badValue(a, min, max)
	if (a is "*") then return false
	try
		set b to a as number
		return ((a is "") or (class of b is real) or (b < min) or (b > max))
	on error
		return true
	end try
end badValue

set aValue to "33.5"
badValue(aValue, 0, 70) -- not "*" and not coercible to integer from 0 to 70?
--> true

In the ‘doDisp()’ handler, there’s no need to set ‘a’ to anything else before setting it to the result of the first ‘display dialog’. Also, since this result is already text, getting the ‘text’ of it is another step you don’t need.

Good luck with the script. :slight_smile:

Nigel, you make this look too easy.

Ok, your code does work better than mine, but both versions have a weird problem on 10.3 where the user can get a “+” sign to be accepted as input. I’ve modified the code and here is the result:


set x to "Please set the time you would like the script to run next for:  " & return & return & ""
script baseComp
	on badValue(a)
		if (a is "*") then return false
		try
			set b to a as number
			return ((a is "+") or (a is "") or (class of b is real) or (b < 0) or (b > 70)) -- added the "+" check for 10.3
		on error
			return true
		end try
	end badValue
end script

set minuteTag to "Use a value from 0 to 59 or * for every Minute:  "

set theMinute to 0
set minuteMsg to x & minuteTag
script minuteComp
	on badValue(a, min, max)
		if (a is "*") then return false
		try
			set b to a as number
			return ((a is "+") or (a is "") or (class of b is real) or (b < min) or (b > max)) -- added the "+" check for 10.3
		on error
			return true
		end try
	end badValue
end script

on doDisp(a, b, c, d, e, f)
	set a to 0
	set a to text returned of (display dialog b default answer "*")
	if text of a is not "*" then
		repeat while (c's badValue(a))
			set a to text returned of (display dialog "You have entered a wrong value.  Please try again." & return & return & b default answer "*")
		end repeat
		repeat while (d's badValue(a, e, f))
			set a to text returned of (display dialog "You have entered a wrong value.  Please try again." & return & return & b default answer "*")
		end repeat
	end if
	return a
end doDisp

set theMinute to doDisp(theMinute, minuteMsg, baseComp, minuteComp, 0, 59)


I realize i should pass the min/max parameters as a property or global–I may do that later. This is just an example.

Thanks again.:smiley:

I think you can also do:

 on badValue(a, min, max)
       if (a is in {"*","+",""}) then return false
       try
           set b to a as number
           return ((class of b is real) or (b < min) or (b > max))
       on error
           return true
       end try
   end badValue

Ah yes. That’s a anomaly that’s been around for a while. It happens on my old OS 8.6 system too. But the ability to coerce plus and minus signs to the number 0 has been specifically stopped as from Tiger. I wish the same were true for empty strings. Your approach will catch these though, as will Reikon’s. (But see my reply to his below.)

I see you’ve used both of the versions I suggested for ‘badValue()’, in script objects, which isn’t what I meant. (But then your script is just a demo that runs two similar tests on the same input.) The script object idea is great for slotting very different test methods into a particular point in the script. To that end, all the script objects’ ‘badValue()’ handlers should take the same number of parameters, so that you don’t have to write different code to call them. If the only differences are the minimum and maximum permitted values ” as in your script ” all the script objects could be replaced by just one instance of ‘badValue()’ (not in a script object), which would take additional parameters defining the limits. You’d then use:

… instead of:

A convenient “middle way” here would be to use script objects with properties defining the minima and maxima. One of the script objects could contain the ‘badValue’ handler and the others could all share it, which would make the script a bit shorter. :slight_smile:

script baseComp
	property min : 0
	property max : 70
	
	on badValue(a)
		if (a is "*") then return false
		try
			set b to a as number
			return ((b is 0 and a is not "0") or (class of b is real) or (b < min) or (b > max))
		on error
			return true
		end try
	end badValue
end script

script minuteComp
	property min : 0
	property max : 59
	-- Set badValue in this script object to mean the badValue in baseComp.
	-- This way seems to hold fewer surprises than when inheriting it through a 'parent' property.
	property badValue : baseComp's badValue
end script

on checkValue(a, method, msg)
	repeat while (method's badValue(a))
		set a to text returned of (display dialog "You have entered a wrong value.  Please try again." & return & return & msg default answer "*")
	end repeat
	return a
end checkValue

on doDisp(b, c, d)
	set a to text returned of (display dialog b default answer "*")
	set a to checkValue(a, c, b)
	set a to checkValue(a, d, b)
	return a
end doDisp

set x to "Please set the time you would like the script to run next for:  " & return & return
set minuteTag to "Use a value from 0 to 59 or * for every Minute:  "
set minuteMsg to x & minuteTag

set theMinute to doDisp(minuteMsg, baseComp, minuteComp)

… except that for waltr’s purposes, it has to return ‘true’ (ie. “Yes, it’s a bad value”) for a plus or an empty string (or a minus), and also for multiple pluses or minuses. A single asterisk is OK; multiple ones aren’t. I think my version above takes care of all that. :cool:

Then change false to true, add the hyphen and voila?

You can also go through each item of the result and make sure it’s within a set limit; such as a certain range of ASCII Decimal values.

You’d need two lines: one to return ‘false’ if the string was “*”, the other to return ‘true’ if it was in {“+”, “-”} or in “±”. But that still wouldn’t catch instances of “++”, “–”, etc.

That would work, and would incidentally take care of reals and negatives too:

script baseComp
	property min : 0
	property max : 70
	
	on badValue(a)
		if (a is "*") then return false
		if (a is "") the return true
		repeat with thisChr in a
			if (thisChr is not in "0123456789") then return true
		end repeat
		set b to a as number
		return ((b < min) or (b > max))
	end badValue
end script

This takes slightly longer to OK valid input (since checking each character of the string takes time), but is faster at turfing out input that can’t be coerced to number (since recovery from an error in my ‘try’ block takes even longer.) In use, the performance differences are unnoticeable. This version would need a couple of extra lines if it had to allow to allow negative numbers too, but it does complain about input like “–2”, which my effort doesn’t. It could be sped up by enclosing all the code in the handler in a ‘considering case’ block. But but perfecting this particular handler is perhaps beyond the scope of the thread. :slight_smile:

What exactly are the asterisks for, and how do you use them? (‘*****’ for five minutes?)

Thanks again Nigel, for the lesson in Script Objects. You’ve opened my eyes to something new in AppleScript. Very Nice.

For Qwerty: The Asteriks mean any as in any minute, any hour, any weekday, etc. Better info can be found in your Terminal window by typing:

man crontab

and

man -S 5 crontab

NOTE: The script we have been discussing would need MAJOR changes to support every option in the cron facility. If that’s what you are after, I’d suggest the freeware product ‘Cronnix’, which you can find on http://versiontracker.com. Cronnix might also give you a better idea about what the asteriks are used for.