Bug in read_default

When I try to use the read_default method it always fails, usually with a syntax error. It looks like the Sketchup libary is running an eval on the value returned from the registry. Naturally, this fails for most values in the registry. For example:

Sketchup.read_default 'preferences', 'ExportInitialDirectory'

returns the following error

Error: #<SyntaxError: <main>: syntax error, unexpected $undefined, expecting tSTRING_CONTENT or tSTRING_DBEG or tSTRING_DVAR or tSTRING_END
C:\Users\Mark\
   ^>
<main>:in `eval'
<main>:in `read_default'
<main>:in `<main>'
SketchUp:1:in `eval'

Is this a known issue, with a workaround? Should I just attempt to read from the registry directly using the Win32::Registry class instead?

That is a known issue, and you have to take care about properly escaping the string that you want to store.

Backslashes \ are almost everywhere escape characters which themselves require to be escaped another time. The safe way of storing file paths is to use the forward slash / which has even been supported by Windows from the beginnings (only that Windows Explorer exposes backslashes to its users).

The write_default method needs escaping (which you should refactor in its own method):

path = "C:\\Users\\Mark"
Sketchup.write_default("author", "key", path.inspect[1...-1])
Sketchup.read_default("author", "key")
# > "C:\\Users\\Mark"

In addition, you can normalize absolute file paths:

File.expand_path(path)
# > "C:/Users/Mark"

or relative paths:

relpath = ".\\file.txt"
relpath.gsub(File::ALT_SEPARATOR || File::SEPARATOR, File::SEPARATOR)
# which does something like  relpath.gsub(/\\/, '/')
# > "./file.txt"

Ruby as well as SketchUp’s Ruby API is cross-platform by default and for free. You would better leave your fingers from the Windows API unless you have already opted out of cross-platform compatibility.

Yes this has been reported, and bugged “forever”.

Generally, on PC, Sketchup::read_default is only guaranteed to work with values that were written by Sketchup::write_default, which always writes plain string values. Expandable Strings and other registry data types, (like DWORD,) will likely not work well.

Also note that the same options settings may be saved a bit differently in the Mac plist file.

Yep, it does for example, so it can convert the string “true” back to the instance true, etc.

So this means because the SketchUp core writes all it’s pathstrings using backslashes (instead of Rubyish forward slashes,) eval fails. Likewise anything with a period or parenthesis will fail.

Basically, currently, it allows a coder to save plugin specific settings and retrieve them. (No other guarantees.) And since we’ve complained for years, and they have not changed it, we might as well believe the development team likes the limitation.

Yep. If you smart enough to know how to use that (for SketchUp 2014+,) or other methods such as WIN32OLE calls, or even Win system calls using Win32API,… then you should be smart enough not to mess things up.

Thanks for the quick replys! Yeah, I was trying to access a value stored by Sketchup core, which I guess is impossible with read_default. If I ever get around to doing it with Win32 or something else, I’ll reply back with some code but I’ll probably just leave it.

Well at one time I did log a Feature Request for an API hash-like interface for user and application levels preferences / options.

I think, I suggested it could be a subclass of Sketchup::OptionsProvider (or an instance of, it’s been a long while.)

Thinking now,… it’d likely need to be it’s own subclass that has the knowledge of how to convert between Ruby classes and registry data types. And it’d need a singleton manager subclass of OptionsManager as well, I would imagine. Call them: PreferencesManager and PreferencesProvider ?

A challenge for the development team is that some of the registry settings get updated “on-the-fly” as they are changed (ie, import export dialog options,) and others do not get updated until the SketchUp application is about to close.

Which can also throw a wrench in your plugin, if you rely upon one of those values that are not dynamically updated.

Haven’t tested this thoroughly, but here is how you can get the same functionality as read_default with a custom method:

require 'win32/registry'

def custom_read_default(section, variable, default = nil)
    keyname = "Software\\SketchUp\\SketchUp 2015\\#{section}"
    Win32::Registry::HKEY_CURRENT_USER.open(keyname) do |reg|
        return reg.map{|key| key}.include?(variable) ? reg[variable] : default
    end
end

Actually, the functionality, is not the same. This custom method does not pass strings through eval(). Which is a good thing for strings that are not evaluation strings. But certain string values like "true" and "false" and string representations of arrays, and some of SketchUp’s API type strings like "Geom::Point3d.new(3,5,6)" will not get eval’d back into objects of their original class, unless you add in a case statement to make a decision based upon the contents of the string.

Just saying …