Code to List and keep track of all my User Attributes much like a Registry?

I have always been hesitant of creating many user attributes. As my file has gotten older, I find the list growing. I currently, manually scroll the list, however this can be hit or miss.

Is there a more automated way using action code or workflow anyone has found to better keep track of their self created User Attribute list?

Thanks in advance
Tom

Actually, there is. You can run a stamp, $MyList = document["user-attributes"];. This will get you all the attributes in your document.

I don’t have time right now, but you could take this list, run another stamp that creates notes for each one and pulls in their type. You could then write a description in that newly crated not and they use yet another step to update the description of all your attributes, defaults, etc. I love where you’re going with this.

@satikusala has already mentioned document[" user-attributes"] to get a list of user attributes. We might then do ths in a new note (as it replaces existing $Text):

var vAttrList = document["user-attributes"];
$Text =;
$Text = "Number of user attributes: " + vAttrList.count + "\n";
var vAttrDetail = "";
vAttrList.each(anAttr){
   $Text = $Text + "\n" + anAttr + "\n";
   var vAttrKeys = attribute(anAttr);
   vAttrKeys.each(aSetting){
      $Text = $Text + "\t" + aSetting.replace(":",": ") + "\n";
   }
};

That just listed 70 user attribute in my current project (the first line of $Text is the count) with per attribute entries like this:

AltName
category: User
sequential:
default:
type: string
suggested:
description: This is used to store the $Name string where a suffix is needed in Tinderbox to ensure a unique $Path and $Name. IOW, $Name might be “Smith & Jones A” but in that case $AltName would be “Smith & Jones”. The AltName can be used when exporting data for other uses.

CitedCount
category: User
sequential:
default: 0
type: number
suggested:
description: Number of times a paper is cited (in conference).

Note that for reason’s I’ve not yet figured the ‘Suggested’ value item list prints one item per line.

See also:

HTH

1 Like

Wow…this was exactly what I was looking for MarkA and MichaelB.
Now the problem is understanding the code. I got lost when you began with the loop variables.
In addition, I assume “anAttr” is a self made name? I assume it can be named whatever you want?
Lost here:

var vAttrDetail = “”;
vAttrList.each(anAttr){
$Text = $Text + “\n” + anAttr + “\n”;
var vAttrKeys = attribute(anAttr);
vAttrKeys.each(aSetting){
$Text = $Text + “\t” + aSetting.replace(“:”,": ") + “\n”;
}
};

Will need to study
Thank you

Tom

One last question: I remember you mentioning on one of the meetups a methodology for automatically stopping an edict with code so that it only runs one time…much like a stamp. For some reason, I cannot find the code.

Thanks
Tom

At the last line of the $Edit put '$EdictDisabled=true;.

Thanks Michael. That helps. Trying to understand the MAGICAL MarkA code above.
Tom

OK, this is an annotated version of the above (from which I’ve deleted an unused variable declaration. Don’t run the version with annotation is (reason further below.)

I think where you are getting lost is the loop variable. It is just a name. I used ‘anAttr’ but it could be ‘x’ or "PlyerTwoHasLost’. It’s just a name you decide when you code the .each() loop. Thus in:

$MyList.each(blinken){

within the loop every case-sensitive use of the word ‘blinken’ (recall, it’s whatever you code), Tinderbox fetches the value of the current list item. so if the list is "ant;bee;cow’, on the first loop the value of blinken is ant, the loop iteration is it bee, etc. As the variable is case sensitive Blinken would hold no value as Tinderbox doesn’t know what that string is for. Remember you told Tinderbox that in this .each() loop that you wish are declaring blinken as the loop variable. You could have chosen x or HovercraftFullOfEels but you chose blinken, because you just did.

You may note that that I tend to use an ‘a’ or ‘an’ prefix to a word that indicates what the loop variable does.

The issue about not using the commented code is that the seem to be a few glitches where although the code colours correctly, Tinderbox is ‘seeing’ and acting on text in the comment. It will get reported. For now, I’d just keep commented and uncommented and run the latter!

Must dash, dinner guests arriving.

2 Likes

Very Helpful MarkA :wave: :pray: :smiley:

1 Like

@mwra—I found what was causing this behavior with your script. It seems that the attribute operator returns data structures that might have changed at some point. In a nutshell, the “suggested” string returned by the attribute() operator is a list with items separated by semicolons, not colons. I assumed this would be different if all of the configuration items for an attribute were retrieved as a dictionary, but that turns out to not be the case. Due to the conflicting interpretation of semicolon delimiters, the keys for the dictionary extend to each suggested string after the first.

While it makes sense for the data structure returned for “suggested” to be a list, that makes it incompatible with the dictionary structure. The lesson I’m taking away from this is, don’t bother retrieving the entire dictionary of an attribute’s configuration. Instead, retrieve each individual configuration item separately. Specifically, string types for type, default, and description, and list type for suggested.

If you’re interested, full details of my experimentation and analysis are presented in the summary.

Summary

Here is the script you provided as an example in response to the original post, but with some LogRec calls inserted to log what was going on when this script executed. The revised script looks like this:

// Example: Capture User Attribute details by Mark Anderson
// First, declare a variable vAttrList to hold the list
// of user attribute names
var:list vAttrList = document ["user-attributes"];
LogRec(StampLog,vAttrList);
// Clean out $Text. We overwrite anyway, but, safety first...
$Text=;
// Next, we report the number of items in the
// list stored in variable vAttrList
$Text = "Number of user attributes: "+ vAttrList.count+" \n";
// Now we iterate (loop) through vAttrList's items
// We declare 'anAttr'as our loop variable, i.e. the
// value of the vAttrList item currently being used in-loop
vAttrList.each(anAttr)
  { // For each attribute, we say what the attribute is called
// i.e., the current value of the anAttr loop variable
LogRec(StampLog,anAttr);
$Text = $Text + "\n" + anAttr + "\n";
// then we make a new variable to fetch the Dictionary of
// attribute () info for the current value of anAttr
var:list vAttrKeys = attribute(anAttr);
LogRec(StampLog, vAttrKeys);
// Now a nested loop to read each discrete key: value
// pair for the attribute name held in anAttr
// "aSetting" is declared as the loop variable
vAttrKeys.each(aSetting)
{ // report the 'key: value' pair, but placing a single space
  // character after the colon for readability
  LogRec(StampLog," => "+aSetting);
  $Text = $Text + "\t" + aSetting.replace(":",": ")+"\n";
  // close the inner loop
};
  // close the outer loop
  };
// The end

When this was put in a stamp, and the stamp executed with an empty note selected, the output was the same as what you encountered previously where each “suggested” entry was on a separate line, as illustrated here:

Number of user attributes: 7 

AnAttributeString
	category: User
	sequential: 
	default: blank
	type: string
	suggested: String-1
	String-2
	String-3
	description: A string for holding attribute details.

The log output provided the clue as seen here:

Log book <StampLog> created Fri, 21 Jan 2022 22:23:00 -0500
00001 | AnAttributeString;DatestampLogs;IndexLogs;LoggingEnabled;Test_1;Test_2;TimestampLogs
00002 | AnAttributeString
00003 | category:User;sequential:;default:blank;type:string;suggested:String-1;String-2;String-3;description:A string for holding attribute details.
00004 | => category:User
00005 | => sequential:
00006 | => default:blank
00007 | => type:string
00008 | => suggested:String-1
00009 | => String-2
00010 | => String-3
00011 | => description:A string for holding attribute details.

The 00003 log entry above shows what was placed into vAttrKeys, which is the complete dictionary for the indicated attribute as produced with this code:

var:list vAttrKeys = attribute(anAttr); LogRec(StampLog, vAttrKeys);

In the log record at index 00003, the string associated with “suggested” is reproduced here:

suggested:String-1;String-2;String-3

So, the inner loop is processing each key in the dictionary retrieved for the attribute. However, since the entries for “suggested” are delimited by semicolons, after the first entry, the others look like new keys, which is why these were printed on multiple lines.

1 Like

Ah, this is us being bitten by a known limitation with Dictionary type data in that it can include a value for a key:value; pair that is actually a list. The result is the key:value pair data look like this with a 5-item list as the value:

X:value1;value2;value3;value4;value5;

Currently a Dictionary can store that but Action code can’t read it back with fidelity. Instead of the key ‘X’ returning the list value1;value2;value3;value4;value5;, it returns just value1. The other items are now essentially key-less values. If we iterate the keys we get:

X:value1;
value2;
value3;
value4;
value5;
Y: value;
etc.

thus showing the problem.

My hunch is this is caused by Tinderbox parsing the dictionary first as a list and then parsing the list items into discrete keys and values. If the Dictionary were parsed first for keys, then list-based values would be less problematic. A key is the string preceding a colon back either string start or a semicolon. Of course if someone puts a colon in a value (or a key, further hilarity will ensue). And that’s before someone decides they need to store a dictionary as the value of another dictionary. :slight_smile:

So in terms of reading list-based attribute() Dictionary values this is not possible in simple terms in action code, pending a fix to action code operators. Writing values, list-based or otherwise, should be possible for a subset of the keys but that functionality is also broken as present (and has been reported as such).

1 Like