Page 6 of 6

Re: which do you think is better user experience?

Posted: Wed Mar 01, 2017 12:22 pm
by Kazinsal
rdos wrote:Let's face it, most of this hidden stuff is to hinder user control and to lock-down you to specific OSes.
Actually most of it is to try to stop people who think they know more than they actually do from bricking their system and then blaming Windows.

Re: which do you think is better user experience?

Posted: Sat Mar 04, 2017 3:40 am
by Sik
Going baaaaaaaaaack to the original topic, just decided to come up with different ways for writing the syntax of some "beep" command that gives you some control over the generated sound. Let's see which one fares better.

Remember, I won't make room for syntax highlight since you'll need everything to follow that for it to be effective (e.g. text editors), not just the shell. I already said earlier why I think that's a bad idea (every executable needs to somehow communicate their syntax to absolutely everything that may need it in order for highlight to work, and that's going to be a lot of effort when it's possible there's an easier alternative that works as well).

Unix-like... yeah not very readable

Code: Select all

beep -f 440 -v 100,0 -t 500 -w sawtooth
If we ditched single-letter arguments and used pronunceable ones. Even if some are shortands, they should still be easier to remember since when it comes to text we're better at remembering how to pronounce them than the exact spelling.

Code: Select all

beep -freq 440hz -vol 10db,0db -time 500ms -wave sawtooth
Using the equals sign strictly, which was suggested earlier

Code: Select all

beep frequency=440hz volume=10db,0db time=500ms waveform=sawtooth
The same but without the equals sign, which was suggested earlier too... this one was meant to rely on syntax highlighting, actually I find it harder to read because without the highlighting and without any sort of syntax that stands out (like the prefix or the equals sign) it becomes really hard to tell apart where does each argument start.

Code: Select all

beep frequency 440hz volume 10db,0db time 500ms waveform sawtooth
Taken to the logical extreme, where each argument is interpreted directly from their format. May seem elegant at first, but while it works here (since every argument is its own kind and hence has its own format, note that they can come in any order), it probably won't do as well on other programs (e.g. if you can take both input and output filenames, it can be hard to tell them apart short of enforcing an order or some obtuse syntax). Consistency would end up being thrown out of the window long term.

Code: Select all

beep sawtooth 440hz 10db~0db 500ms
Probably something to take into account is that the average user will never use the command line, so you can expect the actual users to be willing to put some more effort to learn than usual. If increasing the learning curve a bit can make things much easier in the long term, then it's likely worth the effort. Anyway, leaving these to discuss, maybe somebody can come up with another suggestion.

Also something I hope nobody takes seriously:

Code: Select all

echo '{
   frequency: { start: 440; end: 440; unit: "Hz"; },
   volume: { start: 10; end: 0; unit: "dB"; },
   duration: { value: 500; unit: "ms"; },
   waveform: { shape: "sawtooth"; duty: 0.5; }
}' | beep

Re: which do you think is better user experience?

Posted: Sat Mar 04, 2017 9:26 am
by Schol-R-LEA
You missed some other possibilities, all of which work by basically replacing your shell with a programming language interpreter. The first is to use the conventional function call syntax, and treat arguments as functions themselves:

Code: Select all

beep(frequency(440hz), volume(10db,0db), time(500ms), waveform(sawtooth))
This also has the advantage of making the beginning and ending of the program invocation itself clear, and regularizing the whole structure. However, aside form the extra typing, most non-programmers would probably find it no less confusing than any of the previous examples.

Then next possibility - which I am including mainly for completeness, and because it appeals to me personally - is to use the Lisp keyword conventions (with or without parens around the whole thing):

Code: Select all

beep :frequency 440hz :volume (10db,0db) :time 500ms :waveform sawtooth
I expect that this would be pretty confusing to most people, but it does differentiate parameter names from arguments.

Next, something based on the Smalltalk messaging passing model, where the program is considered the object (which doesn't make sense from an OO viewpoint, but bear with me):

Code: Select all

beep frequency: 440hz volume: [10db,0db] time: 500ms waveform: sawtooth
On the surface, this is basically the same as the previous one, but again, please bear with me.

Finally, here's the 'real' answer in my view: have the installation process include giving a description of the program's 'function signature' (that is, all its arguments and their expected values) in a format such as JSON which can be automagically passed to the command language's REPL. This also neatly allows you to replace the shell language with something like Python or Smalltalk by giving the automatic tools to read program signatures from a database.

Or you could, you know, spend more time on things that casual users - the ones most likely to get confused, but also least likely to use the shell - actually need to be better. Pareto principle, yo.

(Well, unless the 20% problem happens to be "using the shell", but you might want to find that out first.)

Re: which do you think is better user experience?

Posted: Sat Mar 04, 2017 1:32 pm
by Sik
Schol-R-LEA wrote:You missed some other possibilities, all of which work by basically replacing your shell with a programming language interpreter.
I know some people just outright replace traditional shells with Python. In fact when I finally looked at PowerShell I was surprised (given how much praise it gets) that it didn't look more like a traditional scripting language, it still screamed shell all over the place. I'd have thought people hated old-school shell syntax.

I guess it maybe simply isn't very nice when you just want to run a bunch of programs.

Re: which do you think is better user experience?

Posted: Fri Mar 10, 2017 3:56 pm
by OSwhatever
Sik wrote:
Schol-R-LEA wrote:You missed some other possibilities, all of which work by basically replacing your shell with a programming language interpreter.
I know some people just outright replace traditional shells with Python. In fact when I finally looked at PowerShell I was surprised (given how much praise it gets) that it didn't look more like a traditional scripting language, it still screamed shell all over the place. I'd have thought people hated old-school shell syntax.

I guess it maybe simply isn't very nice when you just want to run a bunch of programs.
While I like the idea of having Python as scripting Language in a shell, what I wonder how they do it so that it doesn't become too inconvenient. Do they augment the syntax at all?

For example in a shell always typing call["MyCommand", "-myoption"] at the command prompt can be tiresome. Can you actually change the syntax so that only writing "MyCommand -myoption" doesn't invade on the regular Python syntax. Or is it only script files Python kicks in?

Re: which do you think is better user experience?

Posted: Thu Nov 16, 2017 4:09 pm
by Wajideus
my opinions on usability are a bit more abstract from the norm.

every aspect of a tool can be broken down into 4 things:
  • a set of commands that perform actions
  • a controller, which accepts input from a user or program to execute commands
  • a state, which is initialized from a file or command line options, changes throughout the runtime of the program, and may or may not persist by altering the initialization file when the tool is terminated
  • a presentation of the state, usually visual
for example, when you run 'firefox google.com', the url name alters the initialization of the state of the program. the rendered page itself is a presentation of the state. the navigation buttons, url and search bars, menu, and tabs are all presentations of the controller, which also accepts key bindings like 'Ctrl-R' as alternative forms of input. Ideally, you'd be able to disconnect the controller and view just the page itself in a window.


When it comes to the implementation of a command interpreter, I'm in favor of a Smalltalk-like syntax:

Code: Select all

# this executes /commands/files/copy
files copy: file1, file2 to: directory/
it doesn't lend itself well to controlling the initialization of the state of the program like the traditional command syntax does, but I consider that to be a separate issue which can be resolved in alternative ways like doing:

Code: Select all

# this executes /commands/execute
execute: [files copy: file1, file2 to: directory/] withOptions: force, recursive
where the command inside of the brackets is lazily evaluated or perhaps passed as a syntax tree to the 'execute' command, much like how lisp macros work.


Edit:
From a linguistic standpoint, I see commands as verbs and arguments as nouns, which are grouped by prepositions. options are like adverbs.

Re: which do you think is better user experience?

Posted: Fri Dec 01, 2017 1:53 am
by ggodw000
did not expected this thread has gone this long, i guess it is controversial! Anyways, i have made a sophisticated python based cmd line processing API and has been using it conveniently. It has been bothering me a lot to do a lot of coding in every one of python 50+ something python tool with each has its own parameters. Now with this well documented and works like a magic (almost), but problems I trashed and has been mixing around the words "switches, parameters and arguments." Will have to work on it. Any consumer of this API will almost instantly call this function to process its parameters with few declarations. Posted the whole thing below.

There might be some weird convoluted code relating to first two arg-s in the cmdline sys.argv[1] and sys.argv[2], by convention, almost all of the tools I maintain is in the form:
<script_name> <mgmt_ip> <server_location> --<param1> <value2....N> --<param2> <param3>... etc.,

Some cool features
- All param-s can be in any order and makes no difference except first two. Static order has been real pain.
- All starts with --<param1> and can have optional values following.
- Optional values can be either pre-defined set or any value with pre-defined list lenth.
etc.,

Code: Select all

#   Prepares, processes command line arguments.
#   The pSysArgv is the all values of sys.argv as it is. This is what the
#   users of particular enters in the command line.
#   The pSupportedArgv is the tuple list of supported arguments in dictionary format passed
#   on by scripts and therefore its contents are specific to a particular script's implementation.
#   However following universal convention are used throughout to enable uniform processing
#   for any or almost any type of switches and arguments.
#   Dictionary key holds the name of the argument in the naming convention: "--<argName>"
#   Dictionary value holds the tuple list object, serving as an initial indicator implying how many
#   values the arg needs (refer to examples below):
#   Current implementation divides the tuple values as two types:
#   1. (<value1>, <value2>) - implies literal processing, which means the supportedArgs lists out the
#   actual values that user can enter in the command line for that particular switches and no other
#   allowed value. Example is if particular scripts --drive switches accepts only C: D: E: drive
#   letters and any other drive letters are not allowed.
#   2. ('__range__', <list>) - implies that there is no restrictions on the values provided by user
#   for particular switch, however the <list> contains the range of number of allowed values for that
#   particular switch. This is useful in case, it is not known what value user will be putting at the
#   command line or there too many possibilities for particular variable.
#   For example, if --ip switches requires one value which is IPv4 address but the range of valid
#   IPv4 is too many to listed out as literal however --ip will accept only one value, in this case
#   supported switch is declared as: ('__range__', 1)
#   if --ip requires anywhere between 1 to 5 IP address following, then it should be declared as
#   ('__range__', 1,2,3,4,5)
#
#   pSupportedArgs typical examples are below but not limited to:
#   -------------------------------------------------------------------------------------
#   Arg name     Initial indicator of value count, tuple list type   Valid user input
#   -------------------------------------------------------------------------------------
#   '--timeout': (1,),  (needs one and only one value for argument)
#                                                           -> user input --timeout 100
#   '--timeout': ()     (needs no value)                    -> user input --timeout
#   '--timeout': (1,2,3)(needs somewhere between 1 to 3 values, inclusive)
#                                                           -> user input --timeout 1
#                                                           -> user input --timeout 1 100 2
#                                                           -> user input --timeout 1 100
#   !!! This is currently unimplementable, needs to look!!! Currently it is not supported.
#   '--timeout': [1:CONFIG_MAX_ARG_VALUES] (needs somewhere between 1 to CONFIG_MAX_ARG_VALUES
#   number of values, which effectively implies "greater than 1")
#                                                           -> user input --timeout 1
#                                                           -> user input --timeout 1 100 2
#                                                           -> user input --timeout 1 100
#   !!! This is currently unimplementable, needs to look!!! Currently it is not supported.
#   '--timeout': [1:CONFIG_MAX_ARG_VALUES] (needs somewhere between 1 to CONFIG_MAX_ARG_VALUES
#   number of values, which effectively implies "greater than 1")
#                                                           -> user input --timeout 1
#                                                           -> user input --timeout 1 100 2
#                                                           -> user input --timeout 1 100
#                                                           -> user input --timeout 1 2 3 4 5 6 7
#                                                           -> user input --timeout 1 2 3 4 5 6 7 10 11
#   -------------------------------------------------------------------------------------
#   Once the arguments are processes and validated, the values of the dictionary will be populated
#   with the user supplied values from the command line. If user has not provided the argument, its
#   corresponding dictionary value remain unchanged, tuple list type, implying that the caller script
#   will not process it.
#
#   input:
#   - pSysArgv - sys.argv() as it is.
#   - pSupportedArgs - dictionary type consisting of tuple list of argument that scripts supports.
#       - key holds the name of the argument.
#       - tuple list type holding indicator of how many values are needed for the argument.
#   - helpStringArr - if user input --help argument, use this string to display the help message.
#   output:
#   - pSupportedArgs' values are populated with the validated command line argument values as list type.
#   - if particular argument is not provided by user, value remains unchanged as list tuple type.
#   - EXIT_NO_ERR if --help is supplied.
#     EXIT_ERR on any error condition.
def prepArgs(pSysArgv, pSupportedArgs, helpStringArr):
    debug = 0
    idx = 0                         # holds the index of supported argument being processed during the loop.
    RANGE_CHECK = 1                 # if this value is assigned, each switch's child parameter will be checked against the range specified in th$
    VALUE_CHECK = 2                 # if this value is assigned, literal values in the supportedArgs will be checked against.
    argCheckFlag = VALUE_CHECK
    values = None
    firstSwitchCheck = 0

    if debug:
        printDbg("pSysArgv: ")
        printSeq(pSysArgv, 4)
        printDbg("pSupportedArgs: ")
        printSeq(pSupportedArgs, 4)

    # Iterate through each command line args-.

    for i in range(0, len(pSysArgv)):
        argCheckFlag = VALUE_CHECK
        printDbg("------------------------------------------------ ---", debug)
        #printDbg(pSysArgc[i]: " + str(pSysArgv[i]), debug)
        pSysArgv_i = pSysArgv[i]
        printVars([i, firstSwitchCheck, pSysArgv[i]])

        if re.search("--", pSysArgv[i]):
            printDbg("Processing " + str(pSysArgv[i]), debug)
            printDbg("From : " + str(pSysArgv), debug)

            # If this is help, then display help and quit.

            if pSysArgv[i] == '--help':
                printHelp(helpStringArr)
                return EXIT_NO_ERR

            # Make sure it is supported arg.

            if not pSysArgv[i] in pSupportedArgs.keys():
                printErr(pSysArgv[i] + " is not supported arguments.")
                printErr("Supported args: ")
                printSeq(pSupportedArgs, 4)
                return EXIT_ERR

            # Get the index of matching arg from supportedArg dictionary,

            idx = pSupportedArgs.keys().index(pSysArgv[i])

            printDbg("idx (index of " + str(pSysArgv[i]) + " in pSupportedArgs.keys(): " + str(idx), debug)

            if not type(pSupportedArgs.values()[idx]) == tuple:
                printErr("Wrong type for supported Arg values: ")
                print type(pSupportedArgs.values()[idx])
                return EXIT_ERR

            # If value is none then this specific parameters takes no values,
            # If not none, process further.

            if len(pSupportedArgs.values()[idx]) == 0:
                printDbg("No values are needed for " + str(pSysArgv[i]), debug)
                pSupportedArgs[pSysArgv[i]] = 1
            else:

                # sysArgvCurrToNextDashDash will extract first two letters of all arguments from
                # sysArgv's members ranging from i+1 (since i-th one we know already contains --)
                # This is used in finding a next occurrence of argument starting with -- so that
                # all values between current argument and next occurrence will be assumed for values
                # for current arg.

                sysArgvCurrToNextDashDash = [j[0:2] for j in pSysArgv[i+1:]]
                sysArgvCurrToNext = [j[:] for j in pSysArgv[i+1:]]
                printDbg("sysArgvCurrToNextDashDash: " + str(sysArgvCurrToNextDashDash), debug)

                if '--' in sysArgvCurrToNextDashDash:

                    # Once it is found (the next occurrence), the idxEnd will hold the index of sysArgvCurrToNextDashDash
                    # Note that idxEnd is not the index from the beginning of all arguments, the idxEnd is relative to
                    # current iteration of sysArgv whereas idxEndAbs will find the absolute index value from all the way
                    # to the beginning of sysArgv.
                    # If no argument with -- is found as next occurrence, we assume all the args between current one to the
                    # end of sysargv are considered values.

                    idxEnd = sysArgvCurrToNextDashDash.index('--')
                    idxEndAbs = idxEnd + i + 1
                    printDbg("Further occurrence of -- found, taking its index: " + str(idxEnd), debug)
                else:
                    idxEnd = len(sysArgvCurrToNextDashDash)
                    idxEndAbs = len(pSysArgv)
                    printDbg("No further occurrence of -- found, taking index of last member of sysArgvCurrToNextDashDash", debug)

                printDbg("idxEnd: "  + str(idxEnd), debug)
                printDbg("idxEndAbs: "  + str(idxEndAbs), debug)

                currParamSuppValues = pSupportedArgs.values()[idx]

                printVar(currParamSuppValues, debug)
                if currParamSuppValues[0] == '__range__':
                    printDbg("argCheckFlag: Range check flag is set.", debug)
                    currParamSuppValues = currParamSuppValues[1:]
                    argCheckFlag = RANGE_CHECK

                else:
                    printDbg("argCheckFlag: not changed.")

                printVar(currParamSuppValues, debug)
                pSupportedArgs.values()[idx] = currParamSuppValues

                # Check to see if the values are in the accepted range of values. This check is only done with non-integer values.

                if len(sysArgvCurrToNext[:idxEnd]) == 0 and len(currParamSuppValues) != 0:
                    printErr("1. " + pSysArgv[i] + " accepts range of values: ")
                    printErr(str(currParamSuppValues))
                    return EXIT_ERR
                for m in sysArgvCurrToNext[:idxEnd]:
                    if argCheckFlag == VALUE_CHECK:
                        printDbg("Literal value check...", debug)

                        if not m in currParamSuppValues:

                            # Special parameters where user input does not have to match the pre-defined values for specific param-s.

                            print "Checking if ", m, " in the range..."
                            printErr(pSysArgv[i] + ":" + str(m) + " is not in the accepted range of values: ")
                            print currParamSuppValues
                            return EXIT_ERR
                    elif argCheckFlag == RANGE_CHECK:
                        printDbg("Range check...", debug)

                        if not (idxEnd) in currParamSuppValues:
                            printErr("2. " + pSysArgv[i] + " accepts range of value: ")
                            printErr(str(currParamSuppValues))
                            return EXIT_ERR
                        else:
                            if debug:
                                printDbg("Range check OK: idxEnd" + str(idxEnd) + " is within ", debug)
                                printN(currParamSuppValues)

                    else:
                        printErr("Unsupported values for argCheckFlag: " + str(argCheckFlag))
                        return EXIT_ERR

                    values = pSysArgv[i+1:idxEndAbs]

                    printDbg("values/pSysArgv[" + str(i+1) + ":" + str(idxEndAbs) +  "]: ", debug)
                    print values

                pSupportedArgs[pSysArgv[i]] = values

                if debug:
                    printDbg("pSupportedArgs at the end of this loop: ", debug)
                    print pSupportedArgs
        else:
            printDbg("Not starting with --, doing add'l check.")


            if i == 0:
                printDbg("1. Skipping " + str(pSysArgv[i]), debug)
            elif firstSwitchCheck == 1:
                printDbg("2. Skipping " + str(pSysArgv[i]), debug)
            elif validateIp(pSysArgv[i]):
                printDbg("3. Skipping " + str(pSysArgv[i]), debug)
            elif validateBladeLoc(pSysArgv[i]):
                printDbg("4. Skipping " + str(pSysArgv[i]), debug)
            else:
                printDbg("Previous switch check.")
                if validateIp(pSysArgv[i-1]) or validateBladeLoc(pSysArgv[i-1]):
                    printErr("Expected switch (starting with --) after blade location or UCSM IP: ")
                    return EXIT_ERR

                firstSwitchCheck += 1
    if debug:
        printDbg("Returning pSupportedArgs: ")
        printSeq(pSupportedArgs, 4)
    return pSupportedArgs


Consumer of this API is only few declarative lines, with few use cases below:

Code: Select all

 ...
    helpParamStr = ""

    PARAM_HELP =        '--help'
    PARAM_IP_SRC =      '--ip-src'
    PARAM_FILENAME =    '--filename'

    helpParamList = {\
        PARAM_HELP :        'Display help',\
        PARAM_IP_SRC :      'IP address of the server from which to copy the file from',\
        PARAM_FILENAME :    'Name of the file to copy',
    }

    ...
    # Assign cmd line param-s, process and validate the params.

    t1 = tuple([1])
    argsSupported = {\
        PARAM_HELP: (), PARAM_FILENAME: ('__range__', 1), PARAM_IP_SRC: ('__range__', 1) }
    argsSupported =  prepArgs(sys.argv, argsSupported, helpStringArr)

    if argsSupported == EXIT_NO_ERR:
        quit(0)
    if argsSupported == EXIT_ERR:
        printErr("Error in input, use --help for proper input.")
        quit(1)

    fileToCopy  = argsSupported[PARAM_FILENAME][0]
    option3 = argsSupported[PARAM_IP_SRC][0]

    if validateIp(option3):
        fileCopySrcIp = option3
        printDbg("tftp-server: " + str(option3))

...
Another example:

Code: Select all

    PARAM_HELP =        '--help'
    PARAM_SPNAME =      '--sp'

    helpParamList = {\
        PARAM_HELP :        'display help',\
        PARAM_SPNAME :      'service profile name to be associated with.',
    }
    # Construct supported parameter list.

    t1 = tuple([1])
    argsSupported = {\
        PARAM_HELP: (), PARAM_SPNAME: ('__range__', 1) }
    argsSupported =  prepArgs(sys.argv, argsSupported, helpStringArr)

    if argsSupported == EXIT_NO_ERR:
        quit(0)
    if argsSupported == EXIT_ERR:
        printErr("Error in input, use --help for proper input.")
        quit(1)

    spNewName = argsSupported[PARAM_SPNAME][0]
Last example, this one used slightly pre-mod version of API some other place however it is similar:

Code: Select all

PARAM_LIST_DISKS =      '--disks'
PARAM_LIST_DURATION =   '--time'
PARAM_HELP =             '--help'
PARAM_INFO =            '--info'

helpParamList = {\
    PARAM_LIST_DISKS:       'List of disks to be tested with benchmark.',\
    PARAM_LIST_DURATION:    'duration in minutes to run each benchmark iteration.',\
    PARAM_INFO:             'User provided information regarding benchmark test.',\
    PARAM_HELP:             'Display help information.'
}


t1 = tuple([1])
configRangeDurationInt = range(0,30)
configRangeDurationStr = []

for i in range(0, len(configRangeDurationInt)):
    configRangeDurationStr.append(str(configRangeDurationInt[i]))
CONFIG_RANGE_DURATION = tuple(configRangeDurationStr)

argsSupported = {\
    PARAM_LIST_DISKS: ("C:","D:","E:","F:","G:","H:","I:","K:","L:","M:","N:","O:","P:","R:"), \
    PARAM_HELP: t1,\
    PARAM_LIST_DURATION: (CONFIG_RANGE_DURATION),\
    PARAM_INFO: tuple(["-"])
    }

argsSupported =  prepArgs(sys.argv, argsSupported, helpStringArr)

if argsSupported == EXIT_NO_ERR:
    quit(0)

if argsSupported == EXIT_ERR:
    printErr("Error processing arguments.")
    quit(1)

if type(argsSupported[PARAM_LIST_DURATION]) != tuple:
    print "PARAM_LIST_DURATION was specified from command line."
    CONFIG_TEST_DURATION_MIN = int(argsSupported[PARAM_LIST_DURATION][0])
if type(argsSupported[PARAM_INFO]) != tuple:
    print "CONFIG_TEST_INFO was specified from command line."
    CONFIG_TEST_INFO = argsSupported[PARAM_INFO][0]

if type(argsSupported[PARAM_LIST_DISKS]) != tuple:
    print "PARAM_LIST_DISKS was specified from command line."

    listPatternOutDisks = []

    for i in argsSupported[PARAM_LIST_DISKS]:
        listPatternOutDisks.append("\t"+i+"\n")


Re: which do you think is better user experience?

Posted: Fri Dec 01, 2017 2:08 am
by ggodw000
OSwhatever wrote:I'm like better having "--option=value" command line as it is more clear what value belongs to which switch. It is also easier to parse if don't allow spaces between the option and the value.

In Linux world, having a single '-' is often used for one letter options and '--' is used for word options, like "-f=filename" or "--inputfile=filename". Often both single and word options are provided so that people don't need to write words over and over again, like a shorthand. That's how it is in those systems and you will not have a swat team in your home if you do it another way, even if Linux nerds often suggest that.
i have decided to forgo --option=value and decided to go --option value because, there could be instances where you can specify:
--option <value1> <value2> .... <valueN> and that would be hard with --option=value convention.
The 3rd example I put above uses list of disks letter as a value to --disks parameter:
--disks C: D: E: --<another_param1> ...

Re: which do you think is better user experience?

Posted: Sat Dec 02, 2017 9:22 am
by ComputerHarshul
Turning back is really hard. So, you may try to program both. (Only if you have enough time for that)
EDIT: 2nd one is looking good (according to me). Reasons:
  • It's easier to parse.
    It's more programmatic.
etc.

However, this is a tough question. (really!) :?: