diff --git a/Tools/nib2cib/Converter+Mac.j b/Tools/nib2cib/Converter+Mac.j index 581f12686..41b1b22fc 100644 --- a/Tools/nib2cib/Converter+Mac.j +++ b/Tools/nib2cib/Converter+Mac.j @@ -132,7 +132,16 @@ { var bold = [nibFont isBold]; - cibFont = [theme valueForAttributeWithName:@"font" inState:[object themeState] forClass:[object class]]; + for (var i = 0; i < themes.length; ++i) + { + cibFont = [themes[i] valueForAttributeWithName:@"font" inState:[object themeState] forClass:[object class]]; + + if (cibFont) + { + source = " (from " + [themes[i] name] + ")"; + break; + } + } // Substitute legacy theme fonts for the current system font if (!cibFont || [cibFont familyName] === CPFontDefaultSystemFontFace) @@ -144,7 +153,6 @@ size = [CPFont systemFontSize]; cibFont = bold ? [CPFont boldSystemFontOfSize:size] : [CPFont systemFontOfSize:size]; - source = " (from theme)" } } diff --git a/Tools/nib2cib/Converter.j b/Tools/nib2cib/Converter.j index f8f747e38..be2a1b346 100644 --- a/Tools/nib2cib/Converter.j +++ b/Tools/nib2cib/Converter.j @@ -44,7 +44,7 @@ ConverterConversionException = @"ConverterConversionException"; CPString outputPath @accessors; CPString resourcesPath @accessors; NibFormat format @accessors(readonly); - CPTheme theme @accessors(readonly); + CPArray themes @accessors(readonly); } + (Converter)sharedConverter @@ -55,7 +55,7 @@ ConverterConversionException = @"ConverterConversionException"; return SharedConverter; } -- (id)initWithInputPath:(CPString)aPath format:(NibFormat)nibFormat theme:(CPTheme)aTheme +- (id)initWithInputPath:(CPString)aPath format:(NibFormat)nibFormat themes:(CPArray)themeList { self = [super init]; @@ -63,7 +63,7 @@ ConverterConversionException = @"ConverterConversionException"; { inputPath = aPath; format = nibFormat; - theme = aTheme; + themes = themeList; } return self; diff --git a/Tools/nib2cib/Jakefile b/Tools/nib2cib/Jakefile index 64f08edd7..18f830a3b 100644 --- a/Tools/nib2cib/Jakefile +++ b/Tools/nib2cib/Jakefile @@ -36,6 +36,8 @@ filedir($BUILD_CJS_NIB2CIB, ["nib2cib"], function() make_objj_executable($BUILD_CJS_NIB2CIB); }); +copyManPage("nib2cib", 1); + task ("build", ["nib2cib", $BUILD_CJS_NIB2CIB]); CLOBBER.include($BUILD_CJS_NIB2CIB); diff --git a/Tools/nib2cib/NSCustomResource.j b/Tools/nib2cib/NSCustomResource.j index 46afc017c..5656f2fbd 100644 --- a/Tools/nib2cib/NSCustomResource.j +++ b/Tools/nib2cib/NSCustomResource.j @@ -47,9 +47,9 @@ var FILE = require("file"); var resourcePath = [aCoder resourcePathForName:_resourceName]; if (!resourcePath) - CPLog.warn("Resource named " + _resourceName + " not found in the supplied resources path."); + CPLog.warn("Resource \"" + _resourceName + "\" not found in the resources path: " + [aCoder resourcesPath]); else - size = imageSize(FILE.join(FILE.cwd(), resourcePath)); + size = imageSize(FILE.canonical(resourcePath)); // Account for the fact that an extension may have been inferred. if (resourcePath && FILE.extension(resourcePath) !== FILE.extension(_resourceName)) diff --git a/Tools/nib2cib/main.j b/Tools/nib2cib/main.j index 7cfc62d40..4796675b7 100644 --- a/Tools/nib2cib/main.j +++ b/Tools/nib2cib/main.j @@ -35,13 +35,16 @@ var FILE = require("file"), SYS = require("system"), FileList = require("jake").FileList, stream = require("narwhal/term").stream, + StaticResource = require("objective-j").StaticResource, DefaultTheme = "Aristo", BuildTypes = ["Debug", "Release"], - DefaultXibFile = "MainMenu.xib"; + DefaultFile = "MainMenu"; var parser = new (require("narwhal/args").Parser)(), - nibInfo = {}; + nibInfo = {}, + appDirectory = "", + resourcesDirectory = ""; function main(args) @@ -68,50 +71,52 @@ function convert(options, inputFile) { inputFile = inputFile || getInputFile(options.args); + getAppAndResourceDirectoriesFromInputFile(inputFile, options); + var outputFile = getOutputFile(inputFile, options.args), - resourcesPath = ""; + configInfo = readConfigFile(options.configFile || "", inputFile), + defaultTheme = null; - if (options.resources) + if (configInfo.plist) { - resourcesPath = FILE.canonical(options.resources); + var systemFontFace = configInfo.plist.valueForKey("CPSystemFontFace"); - if (!FILE.isDirectory(resourcesPath) || !FILE.isReadable(resourcesPath)) - fail("Cannot read resources at: " + resourcesPath); + if (systemFontFace) + [CPFont setSystemFontFace:systemFontFace]; + + var systemFontSize = configInfo.plist.valueForKey("CPSystemFontSize"); + + if (systemFontSize) + [CPFont setSystemFontSize:parseFloat(systemFontSize, 10)]; + + if (!options.defaultTheme) + defaultTheme = configInfo.plist.valueForKey("CPDefaultTheme"); } - var configPath = setSystemFontAndSize(options.configFile || "", inputFile), - themeName = "", - themeDir = options.themeDir || ""; - - if (themeDir) - themeName = FILE.basename(themeDir, FILE.extension(themeDir)); - - themeName = themeName || getDefaultThemeName(); - - if (!themeName) - fail("Could not determine the theme name."); - - var theme = loadTheme(themeName, themeDir); + var themeList = getThemeList(defaultTheme || options.defaultTheme, options), + themes = loadThemes(themeList); CPLog.info("\n-------------------------------------------------------------"); - CPLog.info("Input : " + inputFile); - CPLog.info("Output : " + outputFile); - CPLog.info("Format : " + ["Auto", "Mac", "iPhone"][options.format]); - CPLog.info("Resources : " + resourcesPath); - CPLog.info("Frameworks : " + (options.frameworks || "")); - CPLog.info("Theme : " + themeName); - CPLog.info("Config file : " + (configPath || "")); - CPLog.info("System Font : " + [CPFont systemFontSize] + "px " + [CPFont systemFontFace]); + CPLog.info("Input : " + inputFile); + CPLog.info("Output : " + outputFile); + CPLog.info("Format : " + ["Auto", "Mac", "iPhone"][options.format]); + CPLog.info("Application : " + appDirectory); + CPLog.info("Resources : " + resourcesDirectory); + CPLog.info("Frameworks : " + (options.frameworks || "")); + CPLog.info("Default theme : " + themeList[0]); + CPLog.info("Extra themes : " + themeList.slice(1).join(", ")); + CPLog.info("Config file : " + (configInfo.path || "")); + CPLog.info("System Font : " + [CPFont systemFontSize] + "px " + [CPFont systemFontFace]); CPLog.info("-------------------------------------------------------------\n"); var converter = [[Converter alloc] initWithInputPath:inputFile format:options.format - theme:theme]; + themes:themes]; [converter setOutputPath:outputFile]; - [converter setResourcesPath:resourcesPath]; + [converter setResourcesPath:resourcesDirectory]; - loadFrameworks(options.frameworks, function() + loadFrameworks(options.frameworks, options.verbosity, function() { [converter convert]; }); @@ -131,9 +136,19 @@ function watch(options) directory = options.args[0]; if (!directory) - directory = FILE.isDirectory("Resources") ? "Resources" : "."; + directory = FILE.canonical(FILE.isDirectory("Resources") ? "Resources" : "."); + else + { + directory = FILE.canonical(directory); - directory = FILE.canonical(directory); + if (FILE.basename(directory) !== "Resources") + { + var path = FILE.join(directory, "Resources"); + + if (FILE.isDirectory(path)) + directory = path; + } + } if (!FILE.isDirectory(directory)) fail("Cannot find the directory: " + directory); @@ -185,25 +200,24 @@ function parseOptions(args) .set(true) .help("Ask nib2cib to watch a directory for changes"); - parser.option("-F", "framework", "frameworks") + parser.option("-R", "resourcesDir") + .set() + .displayName("directory") + .help("Set the Resources directory, usually unnecessary as it is inferred from the input path"); + + parser.option("--default-theme", "defaultTheme") + .set() + .displayName("name") + .help("Specify a custom default theme which is not set in your Info.plist"); + + parser.option("-t", "--theme", "extraThemes") .push() - .help("Add a framework to load"); - - parser.option("-R", "resources") - .set() - .help("Set the Resources directory"); - - parser.option("--mac", "format") - .set(NibFormatMac) - .def(NibFormatUndetermined) - .help("Set format to Mac"); - - parser.option("-t", "--theme-dir", "themeDir") - .set() - .help("A .build directory to use for theme attribute values"); + .displayName("name") + .help("An additional theme loaded dynamically by your application"); parser.option("--config", "configFile") .set() + .displayName("path") .help("A path to an Info.plist file from which the system font and/or size can be retrieved"); // parser.option("--iphone", "format") @@ -218,6 +232,15 @@ function parseOptions(args) .set(true) .help("No output"); + parser.option("-F", "framework", "frameworks") + .push() + .help("Add a framework to load"); + + parser.option("--mac", "format") + .set(NibFormatMac) + .def(NibFormatUndetermined) + .help("Set format to Mac"); + parser.option("--version", "showVersion") .action(printVersionAndExit) .help("Show the version of nib2cib and quit"); @@ -254,21 +277,80 @@ function setLogLevel(level) function getInputFile(args) { - var inputFile = args[0] || DefaultXibFile; + var inputFile = args[0] || DefaultFile, + path = ""; - if (!/^.+\.xib$/.test(inputFile)) - inputFile += ".xib"; + if (!/^.+\.[nx]ib$/.test(inputFile)) + { + if (path = findInputFile(inputFile, ".xib")) + inputFile = path; + else if (path = findInputFile(inputFile, ".nib")) + inputFile = path; + else + fail("Cannot find the input file (.xib or .nib): " + FILE.canonical(inputFile)); + } + else if (path = findInputFile(inputFile)) + inputFile = path; + else + fail("Could not read the input file: " + FILE.canonical(inputFile)); - inputFile = FILE.canonical(inputFile); + return FILE.canonical(inputFile); +} - if (!FILE.exists(inputFile) && FILE.basename(FILE.dirname(inputFile)) !== "Resources") - if (FILE.isDirectory("Resources")) - inputFile = FILE.resolve(inputFile, FILE.join("Resources", FILE.basename(inputFile))); +function findInputFile(inputFile, extension) +{ + var path = inputFile; - if (!FILE.isReadable(inputFile)) - fail("Cannot read the input file: " + inputFile); + if (extension) + path += extension; - return inputFile; + if (FILE.isReadable(path)) + return path; + + if (FILE.basename(FILE.dirname(inputFile)) !== "Resources" && FILE.isDirectory("Resources")) + { + path = FILE.resolve(path, FILE.join("Resources", FILE.basename(path))); + + if (FILE.isReadable(path)) + return path; + } + + return null; +} + +function getAppAndResourceDirectoriesFromInputFile(inputFile, options) +{ + appDirectory = resourcesDirectory = ""; + + if (options.resourcesDir) + { + var path = FILE.canonical(options.resourcesDir); + + if (!FILE.isDirectory(path)) + fail("Cannot read resources at: " + path); + + resourcesDirectory = path; + } + + var parentDir = FILE.dirname(inputFile); + + if (FILE.basename(parentDir) === "Resources") + { + appDirectory = FILE.dirname(parentDir); + resourcesDirectory = resourcesDirectory || parentDir; + } + else + { + appDirectory = parentDir; + + if (!resourcesDirectory) + { + var path = FILE.join(appDirectory, "Resources"); + + if (FILE.isDirectory(path)) + resourcesDirectory = path; + } + } } function getOutputFile(inputFile, args) @@ -276,14 +358,16 @@ function getOutputFile(inputFile, args) var outputFile = null; if (args.length > 1) + { outputFile = args[1]; + + if (!/^.+\.cib$/.test(outputFile)) + outputFile += ".cib"; + } else - outputFile = FILE.basename(inputFile, FILE.extension(inputFile)); + outputFile = FILE.join(FILE.dirname(inputFile), FILE.basename(inputFile, FILE.extension(inputFile))) + ".cib"; - if (!/^.+\.cib$/.test(outputFile)) - outputFile += ".cib"; - - outputFile = FILE.absolute(outputFile); + outputFile = FILE.canonical(outputFile); if (!FILE.isWritable(FILE.dirname(outputFile))) fail("Cannot write the output file at: " + outputFile); @@ -291,18 +375,30 @@ function getOutputFile(inputFile, args) return outputFile; } -function loadFrameworks(frameworkPaths, aCallback) +function loadFrameworks(frameworkPaths, verbosity, aCallback) { if (!frameworkPaths || frameworkPaths.length === 0) return aCallback(); frameworkPaths.forEach(function(aFrameworkPath) { + setLogLevel(verbosity); CPLog.info("Loading " + aFrameworkPath); - var frameworkBundle = [[CPBundle alloc] initWithPath:aFrameworkPath]; + try + { + // CPBundle is a bit loquacious with logging, we will defer + // logging its exceptions till later. + setLogLevel(-1); - [frameworkBundle loadWithDelegate:nil]; + var frameworkBundle = [[CPBundle alloc] initWithPath:aFrameworkPath]; + + [frameworkBundle loadWithDelegate:nil]; + } + finally + { + setLogLevel(verbosity); + } require("browser/timeout").serviceTimeouts(); }); @@ -318,6 +414,18 @@ function logFormatter(aString, aLevel, aTitle) return CPLogColorize(aString, aLevel); } +function getThemeList(defaultTheme, options) +{ + var themes = [defaultTheme || getDefaultThemeName()]; + + if (options.extraThemes) + for (var i = 0; i < options.extraThemes.length; ++i) + if (themes.indexOf(options.extraThemes[i]) < 0) + themes.push(options.extraThemes[i]); + + return themes; +} + function getDefaultThemeName() { var themeName = nil, @@ -328,6 +436,7 @@ function getDefaultThemeName() for (var i = 0; i < BuildTypes.length; ++i) { var path = FILE.join(cappBuild, BuildTypes[i], "AppKit", "Info.plist"); + themeName = themeNameFromPropertyList(path); if (themeName) @@ -352,11 +461,36 @@ function themeNameFromPropertyList(path) return themeName; } +function loadThemes(themeList) +{ + var themes = []; + + for (var i = 0; i < themeList.length; ++i) + themes.push(loadTheme(themeList[i], resourcesDirectory)); + + return themes; +} + function loadTheme(themeName, 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) { - cappBuild = SYS.env["CAPP_BUILD"]; + // Try in $CAPP_BUILD + cappBuild = FILE.canonical(SYS.env["CAPP_BUILD"]); if (!cappBuild) fail("$CAPP_BUILD is not set, exiting."); @@ -364,69 +498,76 @@ function loadTheme(themeName, themeDir) if (!FILE.isDirectory(cappBuild)) fail("$CAPP_BUILD does not exist: " + cappBuild) - var baseThemeName = themeName, - pos = themeName.indexOf("-"); - - if (pos > 0) - baseThemeName = themeName.substr(0, pos); - - themeDir = FILE.join(cappBuild, baseThemeName + ".build"); - } - - themeDir = FILE.canonical(themeDir); - - if (!FILE.isDirectory(themeDir)) - fail("Cannot find the theme directory: " + themeDir); - - var themePath = null; - - for (var i = 0; i < BuildTypes.length; ++i) - { - var path = FILE.join(themeDir, BuildTypes[i], "Browser.environment/Resources", themeName + ".keyedtheme"); - - if (FILE.isReadable(path)) + for (var i = 0; i < BuildTypes.length; ++i) { - themePath = path; - break; + var path = FILE.join(cappBuild, BuildTypes[i], blendName); + + if (FILE.isDirectory(path)) + { + themePath = path; + break; + } + } + + // Last resort, try the cwd + if (!themePath) + { + var path = FILE.canonical(blendName); + + if (FILE.isDirectory(path)) + themePath = path; } } if (!themePath) - fail("Could not find the keyed theme data for \"" + themeName + "\" in the directory: " + themeDir); + fail("Cannot find the theme \"" + themeName + "\""); - themePath = FILE.canonical(themePath); - var plist = FILE.read(themePath); + return readTheme(themeName, themePath); +} - if (!plist) - fail("Could not read the keyed theme at: " + themePath); +function readTheme(name, path) +{ + var themeBundle = new CFBundle(path); - // The .keyedtheme file has a header that is data I don't need. Strip it off. - var m = plist.match(/^t;\d+;/); + // By default when we try to load the bundle it will use the CommonJS environment, + // but we want the Browser environment. So we override mostEligibleEnvironment(). + themeBundle.mostEligibleEnvironment = function() { return "Browser"; } + themeBundle.load(); - if (!m || m.length === 0) - fail("Invalid keyed theme data at: " + themePath); + var keyedThemes = themeBundle.valueForInfoDictionaryKey("CPKeyedThemes"); - plist = plist.substr(m[0].length); - plist = CFPropertyList.propertyListFromString(plist); + if (!keyedThemes) + fail("Could not find the keyed themes in the theme: " + path); - var data = [CPData dataWithPlistObject:plist], - theme = [CPKeyedUnarchiver unarchiveObjectWithData:data]; + var index = keyedThemes.indexOf(name + ".keyedtheme"); + + if (index < 0) + fail("Could not find the main theme data (" + name + ".keyedtheme" + ") in the theme: " + path); + + // Load the keyed theme data, making sure to resolve it + var resourcePath = themeBundle.pathForResource(keyedThemes[index]), + themeData = new CFMutableData(); + + themeData.setRawString(StaticResource.resourceAtURL(new CFURL(resourcePath), true).contents()); + + var theme = [CPKeyedUnarchiver unarchiveObjectWithData:themeData]; if (!theme) - fail("Could not unarchive the theme at: " + themePath); + fail("Could not unarchive the theme at: " + path); - CPLog.debug("Loaded theme: " + themePath); + CPLog.debug("Loaded theme: " + path); return theme; } -function setSystemFontAndSize(configFile, inputFile) +function readConfigFile(configFile, inputFile) { - var configPath = null; + var configPath = null, + path; // First see if the user passed a config file path if (configFile) { - var path = FILE.canonical(configFile); + path = FILE.canonical(configFile); if (!FILE.isReadable(path)) fail("Cannot find the config file: " + path); @@ -435,19 +576,14 @@ function setSystemFontAndSize(configFile, inputFile) } else { - // See if we can find an Info.plist in the parent directory of the input file, - // if the input file's directory is "Resources". - var path = FILE.canonical(FILE.dirname(inputFile)); + path = FILE.join(appDirectory, "Info.plist"); - if (FILE.basename(path) === "Resources") - { - path = FILE.join(FILE.dirname(path), "Info.plist"); - - if (FILE.isReadable(path)) - configPath = path; - } + if (FILE.isReadable(path)) + configPath = path; } + var plist = null; + if (configPath) { var plist = FILE.read(configPath); @@ -459,24 +595,14 @@ function setSystemFontAndSize(configFile, inputFile) if (!plist) fail("Could not parse the Info.plist at: " + configPath); - - var systemFontFace = plist.valueForKey("CPSystemFontFace"); - - if (systemFontFace) - [CPFont setSystemFontFace:systemFontFace]; - - var systemFontSize = plist.valueForKey("CPSystemFontSize"); - - if (systemFontSize) - [CPFont setSystemFontSize:parseFloat(systemFontSize, 10)]; } - return configPath; + return {path: configPath, plist: plist}; } function getModifiedNibs(path) { - var nibs = new FileList(FILE.join(path, "*.xib")).items(), + var nibs = new FileList(FILE.join(path, "*.[nx]ib")).items(), count = nibs.length, newNibInfo = {}, modifiedNibs = []; @@ -503,7 +629,22 @@ function getModifiedNibs(path) for (var nib in nibInfo) { if (nibInfo.hasOwnProperty(nib)) + { CPLog.info(">> %s %s", CPLogColorize("Deleted:", "warn"), nib); + + var cib = nib.substr(0, nib.length - 3) + "cib"; + + if (FILE.exists(cib)) + { + if (FILE.isWritable(cib)) + { + FILE.remove(cib); + CPLog.warn("Deleted: " + cib); + } + else + CPLog.info("%s could not remove the file: %s", CPLogColorize("Warning:", "warn"), cib); + } + } } nibInfo = newNibInfo; diff --git a/Tools/nib2cib/nib2cib.1 b/Tools/nib2cib/nib2cib.1 new file mode 100644 index 000000000..3c541435b --- /dev/null +++ b/Tools/nib2cib/nib2cib.1 @@ -0,0 +1,219 @@ +.Dd April 3, 2011 +.Os "Cappuccino" +.Dt NIB2CIB 1 "PRM" +.\"----------------------------------------------------------------------------------------- +.Sh NAME +.\"----------------------------------------------------------------------------------------- +.Nm nib2cib +.Nd convert Interface Builder files to Cappuccino cib files +.\"----------------------------------------------------------------------------------------- +.Sh SYNOPSIS +.\"----------------------------------------------------------------------------------------- +.Nm +--watch +.Op options +.Op Pa directory +.Nm +.Op options +.Op Pa input Op Pa output +.Nm +.Op --version | -h | --help +.\"----------------------------------------------------------------------------------------- +.Sh "DESCRIPTION" +.\"----------------------------------------------------------------------------------------- +.Nm +converts an Interface Builder file into a Cappuccino cib file that can be loaded +into a Cappuccino application. +.Pp +There are two main converting modes in which nib2cib can be used: +.Sy watch mode +and +.Sy single mode. +.\"----------------------------------------------------------------------------------------- +.Ss "Watch Mode" +.\"----------------------------------------------------------------------------------------- +The easiest way to use nib2cib is in +.Sy watch mode. +This is done by using the syntax: +.Pp +.D1 nib2cib --watch [options] Op Pa directory +.Pp +If +.Ar directory +is omitted, a "Resources" directory in the current working directory is watched, +and if no "Resources" directory exists, the current working directory is watched. +.Pp +If +.Ar directory +is passed, is not a path to a "Resources" directory, and there is a "Resources" directory +within +.Ar directory , +then that "Resources" directory is watched. Otherwise +.Ar directory +is watched. This allows you to pass the path to your application directory instead +of its "Resources" directory. +.Pp +In watch mode, +.Nm +goes into an infinite loop and monitors the watched directory for changes to xib/nib +files. There are three types of changes +.Nm +will respond to: +.Bl -tag -hang +.It add, modify +When a new xib/nib file is detected or an existing xib/nib file is modified, it is converted into +a cib file with the same base name. +.It delete +When a xib/nib file is deleted, the corresponding cib file is deleted as well. +.El +.Pp +Ordinarily in watch mode +.Nm +will only display a log line for each file that is added, modified or deleted, and a confirmation +when the corresponding cib has been generated or deleted. You can use the +.Fl v +option in watch mode to display more detailed information when a cib is generated. +.\"----------------------------------------------------------------------------------------- +.Ss "Single Mode" +.\"----------------------------------------------------------------------------------------- +In +.Sy single mode, +you convert a single file xib/nib file into a cib. The only advantages of using single mode over +watch mode are: +.Bl -bullet -width 1n +.It +You may want to view verbose output temporarily to debug the conversion, or use different +options for different source files. +.It +You can specify an output name that is not based on the input name. +.El +.Pp +Most of the time watch mode will be sufficient, but if you do need to use single mode, +.Nm +tries to make it as easy as possible. +.Pp +If you navigate to your application directory and invoke: +.Pp +.D1 nib2cib +.Pp +this will look for "MainMenu.xib" or "MainMenu.nib" in the current directory and then in a "Resources" +directory within the current directory, if such a directory exists. Since these are +the default names used in Cappuccino, this usage covers the default case with no extra work. +.Pp +If your xib/nib file is not named "MainMenu.xib" or "MainMenu.nib", or you want to run +.Nm +from a directory other than the application directory or its "Resources" directory, +you can specify an input path: +.Pp +.D1 nib2cib Pa path +.Pp +Note that +.Pa path +does not need the ".xib" or ".nib" extension, since that is assumed. If you were to invoke: +.Pp +.D1 nib2cib foo +.Pp +this would look for "foo.xib" and "foo.nib" in the current directory and in a "Resources" directory +within the current directory. The output of nib2cib in this case would be "foo.cib" in +the same directory as the source file. +.Pp +If you want +.Nm +to generate a cib file with a different base name or in a different directory than +the input file, you may also specify an output path: +.Pp +.D1 nib2cib Pa input Pa output +.Pp +As with input filenames, you do not need to add the ".cib" extension to the output path, +this is assumed. +.\"----------------------------------------------------------------------------------------- +.Ss "Themes" +.\"----------------------------------------------------------------------------------------- +To do its conversions accurately, +.Nm +needs to access the themes you use. If you are using the standard Aristo theme, there is +nothing extra to do. +.Pp +If you are using a custom default theme, usually you specify this using a +.Ar CPDefaultTheme +item in your application's Info.plist. In this case +.Nm +will read the theme name from the Info.plist. If you set the default theme programmatically +and not in the Info.plist, then you should use the +.Fl \-default-theme +option to specify the theme name. +.Pp +If you are loading additional themes in your code using: +.Pp +.D1 [[CPThemeBlend alloc] initWithContentsOfURL:] +.Pp +then you should inform +.Nm +about it by passing the theme names as +.Fl \-theme Ar name +options, one for each theme that you load. +.Pp +Themes are searched for in the Resources, $CAPP_BUILD/Debug and +$CAPP_BUILD/Release directories. +.\"----------------------------------------------------------------------------------------- +.Sh "COMMON OPTIONS" +.\"----------------------------------------------------------------------------------------- +The following options are available in watch mode or single mode: +.Bl -tag -width 4n +.It Fl v, \-verbose +Displays more information about the internal workings of nib2cib. This can be set multiple +times to increase the amount of information displayed. If passed once, general information +is displayed. If passed more than once, detailed information about individual view conversions +is displayed. +.It Fl R Pa path +NOTE: This option is for the most part no longer necessary. +.Pp +Specifies the path (relative or absolute) to a directory from which images +and custom themes are retrieved. Formerly +it was necessary to use this when your xib/nib contained references to images, but now +.Nm +infers the "Resources" directory from the input file, so usually you will not need to use +this option. +.It Fl \-default-theme Ar name +Specifies the name of the default theme used by your application. This is only necessary +if you are not using Aristo and you have not specified the default theme in your application's +Info.plist. For more information see +.Sy Themes +above. +.It Fl t, \-theme Ar name +Specifies the name of an additional theme to load. May be used multiple times. For more +information, see +.Sy Themes +above. +.It Fl \-config Pa path +Specifies the path to an Info.plist file from which to read configuration information about +your application, such as the system font and default theme. Ordinarily you do not need to +use this option, as +.Nm +uses the Info.plist in the application directory, which is inferred from +the input file. +.It Fl \-quiet +Tells +.Nm +to output nothing. This is useful if you are using +.Nm +in a shell script and are only interested in the return value. +.It Fl \-version +Prints the current version of +.Nm +and immediately exits. +.Nm +in a shell script and are only interested in the return value. +.It Fl h, \-help +Displays +.Nm +usage and options. +.It Fl F +Specify the URL of a framework to load before converting. How this is useful is not actually +known at this time. +.El +.\"----------------------------------------------------------------------------------------- +.Sh "RETURN VALUES" +.\"----------------------------------------------------------------------------------------- +.Nm +returns 0 for a successful conversion and >0 if an error occurred.