Inspect a note's non-default attributes and their source

I sometimes ask myself these questions:

  • For any given note, which attribute values differ from the defaults?

  • For each non-default value, what is the source? Are they set on the note itself or are they inherited? If the latter, what is the path of inheritance?

It would be useful if Tinderbox could answer these questions for me. A couple of use cases:

  • I’ve made some changes to the attributes of this note and would like to copy them to its prototype. Remind me what I’ve done to this note’s attributes.

  • I’d like to understand what this built-in prototype does. Show me the attributes that make it special.

If this is possible today, let me know!

See hasLocalValue(attributeNameStr[, item]).

Unless you use cascading prototypes—allowed but rarely encountered—the inheritance chain of attribute default values is this:

  • document default, as seen in the Document Inspector:system tab or in some cases this default is set in Document Settings.
  • prototype. $Prototype.
  • current note.

So, this is no one-shot solution here. However, hasLocalValue() covers the local scope (bullet #2). Checking the same attribute the note given in $Prototype, if any, for hasLocalValue() will tell you if the prototype note has a local due for that attribute (bullet #2).

So I think this is not complex for check … unless you’ve some byzantine nested prototype set-up.

1 Like

This is less easy. hasLocalValue() is scoped to a single attribute, but there are >400 system attributes plus and user attributes which might be another 100.

What you need perhaps is a non-existent hasLocalValues(scope) operator that for the given note tells you all the system or user attributes that have a non-default value.

Thanks for giving this some thought.

Are you suggesting that I try to write some action code that iterates though all attribute names, filter with hasLocalValue(), and put formatted results somewhere? Or is that not possible at the moment? (I don’t see a way to get a list of all attribute names.)

Hmm. There are over 400 system attributes plus whatever user ones you added. If you’re happy doing action code you also have all you need. If not confident with action code, I’d open Get Info:attributes then select each attribute group in turn and note the names of any attribute name in bold text. The latter indicates that the attribute has a local value.

Want to know what is set in the prototype (as opposed to a note using it, select the prototype and use getInfo.

An operator to ‘just’ do this doesn’t exist and I don’t think anyone has requested such. You could always email in a request to the developer (here is not the place for such direct feature requests, as we’re fellow users). Per later comment, see $LocalAttributes.

OK, thanks for the suggestions!

I should point out than whilst the document() operator’s user-attributes key gives a list of all currently defined user attributes of the active document, there is such method for all system attributes. But these can either be extracted from the XML of the TBX file or from aTbRef’s various listings.

External scripts (AppleScript/JXA) can do this. In looking at old scripts I stumbled on $LocalAttributes, which allows a stamp something like this to produce answers to some of these questions:

create(/Log); // create Log note if doesn't exist
var:string outstr = "Note:     " + $Path + "\n\n";
outstr += "Prototype: " + $Prototype + "\n\n";
$LocalAttributes.each(anAttr){
	outstr += anAttr  + "\n"; // attribute name
	outstr += "value:     " + eval(anAttr) +"\n";
	outstr += "default:  " + attribute(anAttr)["default"];
 	outstr += "\n\n"; // blank line between attributes
}
$Text(/Log) = outstr;

Go to Stamp > Inspect Stamps, create new stamp, and paste in this code. Then select a note and run from Stamps menu to see non-default values of attributes. As written the results are placed in a note named “Log” at root level (the note is created if it does not already exist).

For the built-in Markdown prototype the results look like this on my machine:

Note:     /Prototypes/Markdown

Prototype: 

Color
value:     lighter black
default:  cyan

HTMLBoldEnd
value:     
default:  </b>

HTMLBoldStart
value:     
default:  <b>

HTMLItalicEnd
value:     
default:  </i>

HTMLItalicStart
value:     
default:  <i>

HTMLMarkdown
value:     true
default:  false

HTMLMarkupText
value:     false
default:  true

HTMLParagraphEnd
value:     
default:  </p>

HTMLParagraphStart
value:     
default:  <p>

HTMLPreviewCommand
value:     MultiMarkdown
default:  

LineSpacing
value:     120
default:  100

ParagraphSpacing
value:     4
default:  8

QuickListHangingIndent
value:     0
default:  12

QuickListIndent
value:     0
default:  24

Tabs
value:     0.375;0.375;0.375;0.375;0.375;0.375;0.375;0.375;
default:  0.25;0.25;0.25;0.25;

TextFont
value:     IdealSansSSm-Book
default:  MercurySSm-Book

Ziplinks
value:     false
default:  true

Whoa, I missed $LocalAttributes!

Darn, me too! See $LocalAttributes.

@sumnerg I cribbed your action code and put this in a library note, which does most of what I wanted.

function formatAttribute(x) {
    return x + ": " + eval(x) + " (default: " + attribute(x)["default"] + ")";
}

function showLocals() {
    return $LocalAttributes.sort().collect(x, formatAttribute(x)).format("\n");
}

Now I can command-shift-u and /eval showLocals() to inspect. Thanks for the assist, all!

2 Likes

I’ve added an overdue connection in aTbRef between LocalAttributes and hasLocalValue(attributeNameStr[, item]). In addition, a new article Testing for local attribute values, links to both, indicating that one is testing at per-attribute scope (hasLocalValues()), the other at per-note scope ($LocalAttributes).

The search index, sitemap and source zip file have all been updated. all three now turn up in the search page if I enter ‘local value’.

Modifying format to include prototype values

// Format output for showLocals().
function formatAttribute(x) {
    return x + ": " + eval(x) + " << " + $Prototype + ": "  + eval($Prototype, x) + " << default: " + attribute(x)["default"];
}

// Inspect a note's local variables.  Try command-shift-u and `/eval showLocals()`
function showLocals() {
    return $LocalAttributes.sort().collect(x, formatAttribute(x)).format("\n");
}

I had no idea cascading prototypes were rarely used. I use them: like a note “my fun party” will have Prototype=“FunEvent,” which has Prototype=“Event”. But I don’t think it’s worth complicating these functions to walk the inheritance chain for me because they’re pretty easy to follow.

It turns out that this thread wasn’t so “off the wall” after all. Should it be recategorized to “Tinderbox Tasks,” do you think?

I use them in perhaps half my projects.

How does one get the Command Bar window to expand to display more lines as I see in this entry?

All I can see is this:

I think the listing allows up 3 since of content (or title+ 2 lines of explanation), which is what the linked article’s screen grab shows. The result panel extends to a certain size and then offers scroll bars for the item list if there are too many items to fit in.

In your example, the list appears truncated as there is only one match item to the input ‘query’. However, you can’t see more content about the showLocal() item beyond the 3 lines shown. The idea of the Command Bar is to find items not as a full reference document.

There’s a “press Return to copy to clipboard” message that you can’t see because it’s at the end of the output. I’ve been doing that.

It seems /find allows the window to expand to display 5 items with more available via scroll.

But, alas, /eval allows a miserly three lines and no scroll.

Thanks for the tip on pressing return to copy to clipboard.

How would you modify your code to display the Tabs values on one line? Substitute for semicolons before applying format?

Color: lighter black << : << default: cyan
HTMLBoldEnd: << : << default:
HTMLBoldStart: << : << default:
HTMLItalicEnd: << : << default:
HTMLItalicStart: << : << default:
HTMLMarkdown: true << : << default: false
HTMLMarkupText: false << : << default: true
HTMLParagraphEnd: << : << default:
HTMLParagraphStart: << : << default:
HTMLPreviewCommand: MultiMarkdown << : << default:
LineSpacing: 120 << : << default: 100
ParagraphSpacing: 4 << : << default: 8
QuickListHangingIndent: 0 << : << default: 12
QuickListIndent: 0 << : << default: 24
Tabs: 0.375
0.375
0.375
0.375
0.375
0.375
0.375
0.375
<< : << default: 0.25
0.25
0.25
0.25
TextFont: IdealSansSSm-Book << : << default: MercurySSm-Book
Ziplinks: false << : << default: true

I’d try swapping out ; for ,. Obvs, this is OK for numerical data like $Tabs. Might not be so good if list items may contain a comma.

To do that via action code with a String type output, try this ($Text there so some left side element for clarity) to give a comma+space delimiter in output:

$Text = $Tabs.replace(";",", ");

Takes raw List value of:

0.25;0.25;0.25;0.25;

and gives:

0.25, 0.25, 0.25, 0.25, 

Note the trailing comma: if that offences the eye you can test for it and remove:

if($Tabs.endsWith(";")){
	$Text = $Tabs.replace(";",", ").substr(0,-2);
}else{
   $Text = $Tabs.replace(";",", ");
}

Note that for reasons I don’t understand, this appears to not work. Instead of a comma delimited list you get no change:

$Text = $Tabs.format(",");

Yeah, I couldn’t make much headway improving the list/set formatting in those functions. I don’t quite understand what eval is returning or exactly how string formatting is happening. (eval is the appropriate call to get an attribute value using an attribute name string?).

I notice that

/eval eval(Tags)

presents newline-delimited results, whereas

/eval eval("Tags")

presents semicolon-delimited results. But I’m not sure how to interpret that.

Also

/eval type(eval(Tags))

returns nothing, which I don’t understand. So I think I’ve reached the limits of being useful here. :wink: