mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 13:07:02 +00:00
390 lines
10 KiB
Plaintext
Executable File
390 lines
10 KiB
Plaintext
Executable File
#!/usr/bin/env objj
|
|
|
|
@import <Foundation/CPObject.j>
|
|
@import <Foundation/CPArray.j>
|
|
@import <AppKit/CPTheme.j>
|
|
@import <BlendKit/BlendKit.j>
|
|
|
|
var FILE = require("file"),
|
|
OS = require("os"),
|
|
|
|
DefaultTheme = "Aristo2",
|
|
BuildTypes = ["Debug", "Release"],
|
|
|
|
FILE = require("file"),
|
|
OS = require("os"),
|
|
SYS = require("system"),
|
|
FileList = require("jake").FileList,
|
|
stream = require("narwhal/term").stream,
|
|
Parser = require("narwhal/args").Parser,
|
|
options = null,
|
|
|
|
ImageDescriptionFormat = "<%s> {\n filename: \"%s\",\n size: { width:%f, height:%f }\n}",
|
|
|
|
DumpThemeException = @"DumpThemeException";
|
|
|
|
|
|
function main(args)
|
|
{
|
|
try
|
|
{
|
|
var dumper = [ThemeDumper new];
|
|
[dumper dumpWithArgs:args];
|
|
}
|
|
catch (e)
|
|
{
|
|
if ([e isKindOfClass:CPException])
|
|
colorPrint("red", [e reason]);
|
|
|
|
OS.exit(1);
|
|
}
|
|
}
|
|
|
|
@implementation ThemeDumper : CPObject
|
|
{
|
|
JSObject parser;
|
|
int mark;
|
|
}
|
|
|
|
- (id)init
|
|
{
|
|
if (self = [super init])
|
|
{
|
|
parser = new Parser();
|
|
|
|
// Skip the static data header
|
|
mark = @"@STATIC;".length;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dumpWithArgs:(CPArray)args
|
|
{
|
|
[self parseArgs:args];
|
|
|
|
var themeName = options.args[0] || "",
|
|
themeDir = "";
|
|
|
|
if (themeName && (themeName.lastIndexOf(".blend") === (themeName.length - ".blend".length)))
|
|
{
|
|
themeDir = FILE.canonical(FILE.dirname(themeName));
|
|
themeName = FILE.basename(themeName, FILE.extension(themeName));
|
|
}
|
|
|
|
themeName = themeName || [self getAppKitDefaultThemeName];
|
|
|
|
if (!themeName)
|
|
[CPException raise:DumpThemeException reason:@"Could not determine the theme name."];
|
|
|
|
colorPrint("purple", "Theme name: " + themeName);
|
|
|
|
var theme = [self loadTheme:themeName fromDirectory:themeDir];
|
|
|
|
if (!theme)
|
|
[CPException raise:DumpThemeException reason:@"Could not load the theme \"" + themeName + @"\""];
|
|
|
|
[self dump:theme];
|
|
}
|
|
|
|
- (void)parseArgs:(CPArray)args
|
|
{
|
|
parser.usage("[--no-color] [NAME_OR_BLEND_PATH]");
|
|
|
|
parser.option("--no-color", "colorize")
|
|
.set(false)
|
|
.def(true)
|
|
.help("Colorize the output. Should only be used when dumping to the terminal.");
|
|
|
|
parser.helpful();
|
|
|
|
options = parser.parse(args, null, null, true);
|
|
|
|
if (options.args.length > 1)
|
|
{
|
|
parser.printUsage(options);
|
|
OS.exit(0);
|
|
}
|
|
}
|
|
|
|
- (void)dump:(CPTheme)theme
|
|
{
|
|
var classNames = [theme classNames];
|
|
|
|
classNames.sort();
|
|
|
|
for (var i = 0; i < classNames.length; ++i)
|
|
{
|
|
var className = classNames[i],
|
|
attributes = [theme attributeNamesForClass:className];
|
|
|
|
colorPrint("green", "---------------------------\n" + className + "\n---------------------------");
|
|
attributes.sort();
|
|
|
|
for (var attributeIndex = 0; attributeIndex < attributes.length; ++attributeIndex)
|
|
{
|
|
var attributeName = attributes[attributeIndex],
|
|
values = [[theme attributeWithName:attributeName forClass:className] values],
|
|
states = [values allKeys];
|
|
|
|
colorPrint("violet", attributeName);
|
|
states.sort();
|
|
|
|
for (var stateIndex = 0; stateIndex < states.length; ++stateIndex)
|
|
{
|
|
var state = states[stateIndex],
|
|
value = [theme valueForAttributeWithName:attributeName inState:state forClass:className],
|
|
description = CPDescriptionOfObject(value).replace(/^/mg, " ");
|
|
|
|
colorPrint("cyan", " " + String(state));
|
|
stream.print(description + "\n");
|
|
}
|
|
}
|
|
|
|
stream.print("");
|
|
}
|
|
}
|
|
|
|
// Returns undefined if $CAPP_BUILD is not defined, false if path cannot be found in $CAPP_BUILD
|
|
- (id)findResourceInCappBuild:(CPString)path isDirectory:(BOOL)isDirectory callback:(SEL)callback
|
|
{
|
|
var cappBuild = SYS.env["CAPP_BUILD"];
|
|
|
|
if (!cappBuild)
|
|
return undefined;
|
|
|
|
cappBuild = FILE.canonical(cappBuild);
|
|
|
|
if (FILE.isDirectory(cappBuild))
|
|
{
|
|
var result = null;
|
|
|
|
for (var i = 0; i < BuildTypes.length && !result; ++i)
|
|
{
|
|
var findPath = FILE.join(cappBuild, BuildTypes[i], path);
|
|
|
|
if ((isDirectory && FILE.isDirectory(findPath)) || (!isDirectory && FILE.exists(findPath)))
|
|
result = [self performSelector:callback withObject:findPath];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
- (CPString)findResourceInInstalledFrameworks:(CPString)path isDirectory:(BOOL)isDirectory callback:(SEL)callback
|
|
{
|
|
// NOTE: It's safe to use '/' directly in the path, we're guaranteed to be on a Mac
|
|
var frameworks = FILE.canonical(FILE.join(SYS.prefix, "packages/cappuccino/Frameworks")),
|
|
result = null,
|
|
findPath = FILE.join(frameworks, "Debug", path);
|
|
|
|
if ((isDirectory && FILE.isDirectory(findPath)) || (!isDirectory && FILE.exists(findPath)))
|
|
result = [self performSelector:callback withObject:findPath];
|
|
|
|
if (!result)
|
|
{
|
|
findPath = FILE.join(frameworks, path);
|
|
|
|
if ((isDirectory && FILE.isDirectory(findPath)) || (!isDirectory && FILE.exists(findPath)))
|
|
result = [self performSelector:callback withObject:findPath];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
- (CPString)getAppKitDefaultThemeName
|
|
{
|
|
var callback = @selector(themeNameFromPropertyListAtPath:),
|
|
themeName = [self findResourceInCappBuild:@"AppKit/Info.plist" isDirectory:NO callback:callback];
|
|
|
|
if (!themeName)
|
|
themeName = [self findResourceInInstalledFrameworks:@"AppKit/Info.plist" isDirectory:NO callback:callback];
|
|
|
|
return themeName || DefaultTheme;
|
|
}
|
|
|
|
- (CPString)themeNameFromPropertyListAtPath:(CPString)path
|
|
{
|
|
if (!FILE.isReadable(path))
|
|
return nil;
|
|
|
|
var themeName = nil,
|
|
plist = CFPropertyList.readPropertyListFromFile(path);
|
|
|
|
if (plist)
|
|
themeName = plist.valueForKey("CPDefaultTheme");
|
|
|
|
return themeName;
|
|
}
|
|
|
|
- (CPTheme)loadTheme:(CPString)themeName fromDirectory:(CPString)themeDir
|
|
{
|
|
if (/^.+\.blend$/.test(themeName))
|
|
themeName = themeName.substr(0, themeName.length - ".blend".length);
|
|
|
|
var blendName = themeName + ".blend",
|
|
themePath = "";
|
|
|
|
if (themeDir)
|
|
{
|
|
themePath = FILE.join(FILE.canonical(themeDir), blendName);
|
|
|
|
if (!FILE.isDirectory(themePath))
|
|
themePath = themeDir = null;
|
|
}
|
|
|
|
if (!themeDir)
|
|
{
|
|
var callback = @selector(returnPath:);
|
|
|
|
themePath = [self findResourceInCappBuild:blendName isDirectory:YES callback:callback];
|
|
|
|
if (!themePath)
|
|
themePath = [self findResourceInInstalledFrameworks:@"AppKit/Resources/" + blendName isDirectory:YES callback:callback];
|
|
|
|
// Last resort, try the cwd
|
|
if (!themePath)
|
|
{
|
|
var path = FILE.canonical(blendName);
|
|
|
|
if (FILE.isDirectory(path))
|
|
themePath = path;
|
|
}
|
|
}
|
|
|
|
if (!themePath)
|
|
[CPException raise:DumpThemeException reason:@"Cannot find the theme \"" + themeName + '"'];
|
|
|
|
return [self readTheme:themeName atPath:themePath];
|
|
}
|
|
|
|
- (CPString)returnPath:(CPString)path
|
|
{
|
|
return path;
|
|
}
|
|
|
|
- (CPTheme)readTheme:(CPString)themeName atPath:(CPString)themePath
|
|
{
|
|
var keyedTheme = null,
|
|
staticData = FILE.read(FILE.join(themePath, "Browser.Environment", FILE.basename(themePath) + ".sj"));
|
|
|
|
if (!staticData)
|
|
[CPException raise:DumpThemeException reason:@"Could not find the theme file: " + themePath];
|
|
|
|
try
|
|
{
|
|
keyedTheme = [self readStaticResource:themeName + @".keyedtheme" fromData:staticData];
|
|
|
|
if (!keyedTheme)
|
|
[CPException raise:DumpThemeException reason:@"Could not find the keyed theme data in the theme at: " + themePath];
|
|
}
|
|
catch (ex)
|
|
{
|
|
if (![ex isKindOfClass:CPException])
|
|
[CPException raise:DumpThemeException reason:@"Could not read the theme at: " + themePath];
|
|
}
|
|
|
|
keyedTheme = CFPropertyList.propertyListFromString(keyedTheme);
|
|
|
|
var data = [CPData dataWithPlistObject:keyedTheme],
|
|
theme = [CPKeyedUnarchiver unarchiveObjectWithData:data];
|
|
|
|
if (!theme)
|
|
[CPException raise:DumpThemeException reason:@"Could not unarchive the theme at: " + themePath];
|
|
|
|
colorPrint("purple", "Loaded theme: " + themePath + "\n");
|
|
|
|
return theme;
|
|
}
|
|
|
|
- (JSObject)readStaticResource:(CPString)name fromData:(CPString)staticData
|
|
{
|
|
name = @"Resources/" + name;
|
|
|
|
// After the header is the version
|
|
var version = [self readFloatItemFromData:staticData];
|
|
|
|
while (true)
|
|
{
|
|
var resourceName = [self readStringItemFromData:staticData];
|
|
|
|
mark += 2; // skip "t;"
|
|
|
|
var resourceLength = [self readIntItemFromData:staticData];
|
|
|
|
if (resourceName == name)
|
|
return [self readDataItemFromData:staticData length:resourceLength];
|
|
|
|
mark += resourceLength;
|
|
|
|
if (staticData.substr(mark, 2) == "e;")
|
|
return null;
|
|
}
|
|
}
|
|
|
|
- (int)readIntItemFromData:(CPString)staticData
|
|
{
|
|
var endPos = [self endOfItemInData:staticData],
|
|
value = parseInt(staticData.substring(mark, endPos), 10);
|
|
|
|
mark = endPos + 1;
|
|
|
|
return value;
|
|
}
|
|
|
|
- (float)readFloatItemFromData:(CPString)staticData
|
|
{
|
|
var endPos = [self endOfItemInData:staticData],
|
|
value = parseFloat(staticData.substring(mark, endPos));
|
|
|
|
mark = endPos + 1;
|
|
|
|
return value;
|
|
}
|
|
|
|
- (CPString)readStringItemFromData:(CPString)staticData
|
|
{
|
|
if (staticData.substr(mark, 2) !== "p;")
|
|
[CPException raise:DumpThemeException reason:@"Expected string item, found '" + staticData.substr(mark, 2) + @"'"];
|
|
|
|
mark += 2; // skip "p;"
|
|
|
|
var length = [self readIntItemFromData:staticData],
|
|
value = staticData.substr(mark, length);
|
|
|
|
mark += length;
|
|
|
|
return value;
|
|
}
|
|
|
|
- (int)endOfItemInData:(CPString)staticData
|
|
{
|
|
var endPos = staticData.indexOf(";", mark);
|
|
|
|
if (endPos < 0)
|
|
[CPException raise:DumpThemeException reason:@"Could not find item terminator"];
|
|
|
|
return endPos;
|
|
}
|
|
|
|
- (CPString)readDataItemFromData:staticData length:(int)dataLength
|
|
{
|
|
var data = staticData.substr(mark, dataLength);
|
|
|
|
mark += dataLength;
|
|
|
|
return data;
|
|
}
|
|
|
|
@end
|
|
|
|
function colorPrint(color, message)
|
|
{
|
|
if (options.colorize)
|
|
stream.print("\0bold(\0" + color + "(" + message + "\0)\0)");
|
|
else
|
|
stream.print(message);
|
|
}
|