Object space crawler script

This LUA plugin can list the whole MA object space in a txt file.

```
local cmd = gma.cmd
local get = gma.show.getobj
local handle = get.handle
local class = get.class
local prop = gma.show.property
local log = gma.feedback
local exec = gma.user.getselectedexec()
local filehandle = 0

local encode

local escape_char_map = {
  [ "\\" ] = "\\\\",
  [ "\"" ] = "\\\"",
  [ "\b" ] = "\\b",
  [ "\f" ] = "\\f",
  [ "\n" ] = "\\n",
  [ "\r" ] = "\\r",
  [ "\t" ] = "\\t",
}

local function escape_char(c)
  return escape_char_map[c] or string.format("\\u%04x", c:byte())
end

local function encode_string(val)
  return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end

local _print = function(o)
  --gma.feedback(o)
  --if filehandle == 0 then return end
  fileHandle.write(fileHandle,o)
end
local _println = function(o)
  if o == nil then _print('\n') else _print(o..'\n') end
end

--local rootobjs = {'Showfile','TimeConfig','Settings','DMX_Protocols','NetConfig','CITPNetConfig','TrackingSystems','UserImagePool','RDM_Data','LiveSetup','EditSetup','Macros','FlightRecordings','Plugins','Gels','Presets','Worlds','Filters','FadePaths','Programmer','Groups','Forms','Effect','Sequences','Timers','MasterSections','ExecutorPages','ChannelPages','Songs','Agendas','Timecode','RemoteTypes','DMXSnapshotPool','Layout','UserProfiles','Users','PixelMapperContainer','NDP_Root'}
local rootobjs = {'Exec 1.1 Cue'}
--local rootobjs = {'Showfile','TimeConfig','Settings','DMX_Protocols','NetConfig','CITPNetConfig','TrackingSystems','UserImagePool','RDM_Data','LiveSetup','EditSetup','Macros','FlightRecordings','Plugins','Gels','Presets','Worlds','Filters','FadePaths','Programmer','Groups','Forms','Effects','Sequences','Timers','MasterSections','ExecutorPages','ChannelPages','Songs','Agendas','Timecodes','RemoteTypes','DMXSnapshotPool','Layouts','UserProfiles','Users','PixelMapperContainer','NDP_Root'} -- this does not work due to some 's-es at the endings (weird behaviour)

local blacklistedClasses = {'CMD_RDM_UNIVERSE_COLLECT', 'CMD_DMX_PROFILE', 'CMD_DMX_UNIVERSE'}

function isBlacklisted(class)
	for key, value in pairs(blacklistedClasses) do
		if class == value then return true end
	end
	return false
end

local crawledHandles = {}

function crawlObject(handle, prefix)
	_println(prefix .. '"handle": ' .. string.format("%d", handle) .. ',')

	if crawledHandles[handle] ~= nil then
		_println(prefix .. '"name": "' .. get.name(handle) .. '",');
		_println(prefix .. '"known": true')
		return
	end
	crawledHandles[handle] = true

	local class = get.class(handle)
	_println(prefix .. '"name": "' .. get.name(handle) .. '",');
	_println(prefix .. '"index": ' .. get.index(handle) .. ',');
	_println(prefix .. '"class": "' .. class .. '",');
	_println(prefix .. '"number": "' .. get.number(handle) .. '",');
	_println(prefix .. '"amount": ' .. get.amount(handle) .. ',');
	if isBlacklisted(class) then
		_println(prefix .. '"blacklisted": true');
		return
	end

	if get.label(handle) then _println(prefix .. '"label": "' .. get.label(handle) .. '",') end

	-- properties
  if prop.amount(handle) > 0 then
  	_print(prefix .. '"properties": {')
  	local first = true
  	for propindex = 0, prop.amount(handle) - 1 do
  		local name = prop.name(handle, propindex)
  		if name ~= nil then
	  		if first then
	  			first = false
	  			_println()
	  		else
	  			_println(',')
	  		end

  			local data = prop.get(handle, propindex)

	  		if data == nil then
	  			data = 'null'
	  		else
	  			if type(data) == "string" then
	  				data = encode_string(data)
	  			end
	  		end

	  		_print(prefix .. '  "' .. name .. '": ' .. data)
	  	end
  	end
  	_println()
  	_println(prefix .. "},")
  end

  -- parent
  if get.parent(handle) > 0 then
  	_println(prefix .. '"parent" : {')
  	_println(prefix .. '}')
  end

  -- children
	if get.amount(handle) > 0 then
		_println(prefix .. '"children": [');
		local child = 0
		local first = true
		for childIndex = 0, get.amount(handle) - 1 do
			if first then
		  	first = false
		  else
		  	_println(',')
		  end

			local child = get.child(handle,childIndex)
			if child then
			  _println(prefix .. '  {')
			  crawlObject(child, prefix .. '    ')
		  	_print(prefix .. '  }')
			else
				_print(prefix .. '  null')
			end
		end
		_println()
		_println(prefix .. ']')
	else
		_println(prefix .. '"children": []')
	end
end

return function ()
  fileHandle = io.open("maObjModel.json", "w+")
  _println('{')

  -- counting elements to get approximate size
  h_crawl_progress=gma.gui.progress.start("CRAWL");
  local size = 0
  for i,rootobj in pairs(rootobjs) do
	  if get.handle(rootobj) then
	  	size = size + 1
	 	end
	end
  gma.gui.progress.setrange(h_crawl_progress,1,size);

  -- do the crawl
  local n = 0
  for i,rootobj in pairs(rootobjs) do
  	n = n + 1
	  local handle=get.handle(rootobj)
	  if(handle) then
	    _println('  "'..rootobj .. '":{');
	    gma.gui.progress.set(h_crawl_progress,n)
	    gma.gui.progress.settext(h_crawl_progress, rootobj)
	    crawlObject(handle, '    ')
	    if rootobj == 'NDP_Root' then
	    	_println('  }')
	    else
	    	_println('  },')
	    end
	  else
	    _println(rootobj .. ':null')
	  end
  end

  gma.gui.progress.stop(h_crawl_progress);
  _println('}')
  fileHandle.close(fileHandle)
end

```