diff --git a/Nukefile b/Nukefile
index 20bddc5..4f1849d 100644
--- a/Nukefile
+++ b/Nukefile
@@ -5,7 +5,7 @@
(set SYSTEM ((NSString stringWithShellCommand:"uname") chomp))
(case SYSTEM
("Darwin"
- (set @arch (list "x86_64"))
+ (set @arch (list "x86_64" ))
(set @cflags "-g -std=gnu99 -fobjc-gc -DDARWIN")
(set @ldflags "-framework Foundation -framework Nu -levent -lcrypto"))
("Linux"
@@ -20,12 +20,15 @@
(set @framework "Nunja")
(set @framework_identifier "nu.programming.nunja")
(set @framework_creator_code "????")
-(set @framework_extra_install
- (do () (SH "sudo cp nunjad /usr/local/bin")))
+;(set @framework_extra_install (do () (SH "sudo cp nunjad /usr/local/bin")))
(compilation-tasks)
(framework-tasks)
+(task "clean" is
+ (SH "rm -rf build")
+ (SH "rm -rf Xcode/build"))
+
(task "clobber" => "clean" is
(SH "rm -rf #{@framework_dir}"))
diff --git a/Xcode/English.lproj/InfoPlist.strings b/Xcode/English.lproj/InfoPlist.strings
new file mode 100644
index 0000000..88f65cf
--- /dev/null
+++ b/Xcode/English.lproj/InfoPlist.strings
@@ -0,0 +1,2 @@
+/* Localized versions of Info.plist keys */
+
diff --git a/Xcode/Info.plist b/Xcode/Info.plist
new file mode 100644
index 0000000..4d7a357
--- /dev/null
+++ b/Xcode/Info.plist
@@ -0,0 +1,28 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ English
+ CFBundleExecutable
+ ${EXECUTABLE_NAME}
+ CFBundleIconFile
+
+ CFBundleIdentifier
+ nu.programming.nunja
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ ${PRODUCT_NAME}
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1
+ NSPrincipalClass
+
+
+
diff --git a/Xcode/Nunja.xcodeproj/project.pbxproj b/Xcode/Nunja.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..41fdc5a
--- /dev/null
+++ b/Xcode/Nunja.xcodeproj/project.pbxproj
@@ -0,0 +1,459 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 45;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 2224951111D9BAA300A4D1F2 /* NSFileManager_Nunja.h in Headers */ = {isa = PBXBuildFile; fileRef = 2224950411D9BAA300A4D1F2 /* NSFileManager_Nunja.h */; };
+ 2224951211D9BAA300A4D1F2 /* NSFileManager_Nunja.m in Sources */ = {isa = PBXBuildFile; fileRef = 2224950511D9BAA300A4D1F2 /* NSFileManager_Nunja.m */; };
+ 2224951311D9BAA300A4D1F2 /* Nunja_daemonize.m in Sources */ = {isa = PBXBuildFile; fileRef = 2224950611D9BAA300A4D1F2 /* Nunja_daemonize.m */; };
+ 2224951411D9BAA300A4D1F2 /* Nunja.h in Headers */ = {isa = PBXBuildFile; fileRef = 2224950711D9BAA300A4D1F2 /* Nunja.h */; };
+ 2224951511D9BAA300A4D1F2 /* Nunja.m in Sources */ = {isa = PBXBuildFile; fileRef = 2224950811D9BAA300A4D1F2 /* Nunja.m */; };
+ 2224951611D9BAA300A4D1F2 /* NunjaDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2224950911D9BAA300A4D1F2 /* NunjaDelegate.h */; };
+ 2224951711D9BAA300A4D1F2 /* NunjaDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2224950A11D9BAA300A4D1F2 /* NunjaDelegate.m */; };
+ 2224951811D9BAA300A4D1F2 /* NunjaRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 2224950B11D9BAA300A4D1F2 /* NunjaRequest.h */; };
+ 2224951911D9BAA300A4D1F2 /* NunjaRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 2224950C11D9BAA300A4D1F2 /* NunjaRequest.m */; };
+ 2224951A11D9BAA300A4D1F2 /* NunjaRequestHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 2224950D11D9BAA300A4D1F2 /* NunjaRequestHandler.h */; };
+ 2224951B11D9BAA300A4D1F2 /* NunjaRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 2224950E11D9BAA300A4D1F2 /* NunjaRequestHandler.m */; };
+ 2224951C11D9BAA300A4D1F2 /* NunjaRequestRouter.h in Headers */ = {isa = PBXBuildFile; fileRef = 2224950F11D9BAA300A4D1F2 /* NunjaRequestRouter.h */; };
+ 2224951D11D9BAA300A4D1F2 /* NunjaRequestRouter.m in Sources */ = {isa = PBXBuildFile; fileRef = 2224951011D9BAA300A4D1F2 /* NunjaRequestRouter.m */; };
+ 22C53C3911D9B17F003626DF /* nunja.nu in Resources */ = {isa = PBXBuildFile; fileRef = 22C53C3811D9B17F003626DF /* nunja.nu */; };
+ 22C53C4211D9B1CE003626DF /* libevent.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 22C53C4111D9B1CE003626DF /* libevent.a */; };
+ 22C53C4911D9B1FA003626DF /* Nu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22C53C4811D9B1FA003626DF /* Nu.framework */; };
+ 22C53CCE11D9B21B003626DF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22C53CCD11D9B21B003626DF /* Foundation.framework */; };
+ 22C53CE511D9B261003626DF /* nunjad.m in Sources */ = {isa = PBXBuildFile; fileRef = 22C53CE211D9B256003626DF /* nunjad.m */; };
+ 22C53CEF11D9B2B4003626DF /* Nunja.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* Nunja.framework */; };
+ 22C53D3E11D9B4A8003626DF /* NuHTTPHelpers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22C53D3D11D9B4A8003626DF /* NuHTTPHelpers.framework */; };
+ 8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C1666FE841158C02AAC07 /* InfoPlist.strings */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 22C53CFF11D9B2F8003626DF /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0867D690FE84028FC02AAC07 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 8DC2EF4F0486A6940098B216;
+ remoteInfo = Nunja;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 089C1667FE841158C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; };
+ 2224950411D9BAA300A4D1F2 /* NSFileManager_Nunja.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSFileManager_Nunja.h; path = ../objc/NSFileManager_Nunja.h; sourceTree = SOURCE_ROOT; };
+ 2224950511D9BAA300A4D1F2 /* NSFileManager_Nunja.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NSFileManager_Nunja.m; path = ../objc/NSFileManager_Nunja.m; sourceTree = SOURCE_ROOT; };
+ 2224950611D9BAA300A4D1F2 /* Nunja_daemonize.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Nunja_daemonize.m; path = ../objc/Nunja_daemonize.m; sourceTree = SOURCE_ROOT; };
+ 2224950711D9BAA300A4D1F2 /* Nunja.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Nunja.h; path = ../objc/Nunja.h; sourceTree = SOURCE_ROOT; };
+ 2224950811D9BAA300A4D1F2 /* Nunja.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Nunja.m; path = ../objc/Nunja.m; sourceTree = SOURCE_ROOT; };
+ 2224950911D9BAA300A4D1F2 /* NunjaDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NunjaDelegate.h; path = ../objc/NunjaDelegate.h; sourceTree = SOURCE_ROOT; };
+ 2224950A11D9BAA300A4D1F2 /* NunjaDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NunjaDelegate.m; path = ../objc/NunjaDelegate.m; sourceTree = SOURCE_ROOT; };
+ 2224950B11D9BAA300A4D1F2 /* NunjaRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NunjaRequest.h; path = ../objc/NunjaRequest.h; sourceTree = SOURCE_ROOT; };
+ 2224950C11D9BAA300A4D1F2 /* NunjaRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NunjaRequest.m; path = ../objc/NunjaRequest.m; sourceTree = SOURCE_ROOT; };
+ 2224950D11D9BAA300A4D1F2 /* NunjaRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NunjaRequestHandler.h; path = ../objc/NunjaRequestHandler.h; sourceTree = SOURCE_ROOT; };
+ 2224950E11D9BAA300A4D1F2 /* NunjaRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NunjaRequestHandler.m; path = ../objc/NunjaRequestHandler.m; sourceTree = SOURCE_ROOT; };
+ 2224950F11D9BAA300A4D1F2 /* NunjaRequestRouter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NunjaRequestRouter.h; path = ../objc/NunjaRequestRouter.h; sourceTree = SOURCE_ROOT; };
+ 2224951011D9BAA300A4D1F2 /* NunjaRequestRouter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NunjaRequestRouter.m; path = ../objc/NunjaRequestRouter.m; sourceTree = SOURCE_ROOT; };
+ 22C53C3811D9B17F003626DF /* nunja.nu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = nunja.nu; path = ../nu/nunja.nu; sourceTree = SOURCE_ROOT; };
+ 22C53C4111D9B1CE003626DF /* libevent.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libevent.a; path = /usr/local/lib/libevent.a; sourceTree = ""; };
+ 22C53C4811D9B1FA003626DF /* Nu.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nu.framework; path = /Library/Frameworks/Nu.framework; sourceTree = ""; };
+ 22C53CCD11D9B21B003626DF /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; };
+ 22C53CDE11D9B242003626DF /* nunjad */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = nunjad; sourceTree = BUILT_PRODUCTS_DIR; };
+ 22C53CE211D9B256003626DF /* nunjad.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = nunjad.m; sourceTree = ""; };
+ 22C53D3D11D9B4A8003626DF /* NuHTTPHelpers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NuHTTPHelpers.framework; path = /Library/Frameworks/NuHTTPHelpers.framework; sourceTree = ""; };
+ 32DBCF5E0370ADEE00C91783 /* Nunja_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Nunja_Prefix.pch; sourceTree = ""; };
+ 8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 8DC2EF5B0486A6940098B216 /* Nunja.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Nunja.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 22C53CDC11D9B242003626DF /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 22C53CEF11D9B2B4003626DF /* Nunja.framework in Frameworks */,
+ 22C53D3E11D9B4A8003626DF /* NuHTTPHelpers.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 8DC2EF560486A6940098B216 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 22C53C4211D9B1CE003626DF /* libevent.a in Frameworks */,
+ 22C53C4911D9B1FA003626DF /* Nu.framework in Frameworks */,
+ 22C53CCE11D9B21B003626DF /* Foundation.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 034768DFFF38A50411DB9C8B /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 8DC2EF5B0486A6940098B216 /* Nunja.framework */,
+ 22C53CDE11D9B242003626DF /* nunjad */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 0867D691FE84028FC02AAC07 /* Nunja */ = {
+ isa = PBXGroup;
+ children = (
+ 08FB77AEFE84172EC02AAC07 /* Classes */,
+ 32C88DFF0371C24200C91783 /* Other Sources */,
+ 089C1665FE841158C02AAC07 /* Resources */,
+ 0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */,
+ 034768DFFF38A50411DB9C8B /* Products */,
+ );
+ name = Nunja;
+ sourceTree = "";
+ };
+ 0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */ = {
+ isa = PBXGroup;
+ children = (
+ 22C53CCD11D9B21B003626DF /* Foundation.framework */,
+ 22C53C4811D9B1FA003626DF /* Nu.framework */,
+ 22C53C4111D9B1CE003626DF /* libevent.a */,
+ 1058C7B0FEA5585E11CA2CBB /* Linked Frameworks */,
+ 1058C7B2FEA5585E11CA2CBB /* Other Frameworks */,
+ 22C53D3D11D9B4A8003626DF /* NuHTTPHelpers.framework */,
+ );
+ name = "External Frameworks and Libraries";
+ sourceTree = "";
+ };
+ 089C1665FE841158C02AAC07 /* Resources */ = {
+ isa = PBXGroup;
+ children = (
+ 8DC2EF5A0486A6940098B216 /* Info.plist */,
+ 089C1666FE841158C02AAC07 /* InfoPlist.strings */,
+ );
+ name = Resources;
+ sourceTree = "";
+ };
+ 08FB77AEFE84172EC02AAC07 /* Classes */ = {
+ isa = PBXGroup;
+ children = (
+ 2224950411D9BAA300A4D1F2 /* NSFileManager_Nunja.h */,
+ 2224950511D9BAA300A4D1F2 /* NSFileManager_Nunja.m */,
+ 2224950611D9BAA300A4D1F2 /* Nunja_daemonize.m */,
+ 2224950711D9BAA300A4D1F2 /* Nunja.h */,
+ 2224950811D9BAA300A4D1F2 /* Nunja.m */,
+ 2224950911D9BAA300A4D1F2 /* NunjaDelegate.h */,
+ 2224950A11D9BAA300A4D1F2 /* NunjaDelegate.m */,
+ 2224950B11D9BAA300A4D1F2 /* NunjaRequest.h */,
+ 2224950C11D9BAA300A4D1F2 /* NunjaRequest.m */,
+ 2224950D11D9BAA300A4D1F2 /* NunjaRequestHandler.h */,
+ 2224950E11D9BAA300A4D1F2 /* NunjaRequestHandler.m */,
+ 2224950F11D9BAA300A4D1F2 /* NunjaRequestRouter.h */,
+ 2224951011D9BAA300A4D1F2 /* NunjaRequestRouter.m */,
+ );
+ name = Classes;
+ sourceTree = "";
+ };
+ 1058C7B0FEA5585E11CA2CBB /* Linked Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = "Linked Frameworks";
+ sourceTree = "";
+ };
+ 1058C7B2FEA5585E11CA2CBB /* Other Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = "Other Frameworks";
+ sourceTree = "";
+ };
+ 32C88DFF0371C24200C91783 /* Other Sources */ = {
+ isa = PBXGroup;
+ children = (
+ 22C53CE211D9B256003626DF /* nunjad.m */,
+ 22C53C3811D9B17F003626DF /* nunja.nu */,
+ 32DBCF5E0370ADEE00C91783 /* Nunja_Prefix.pch */,
+ );
+ name = "Other Sources";
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ 8DC2EF500486A6940098B216 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 2224951111D9BAA300A4D1F2 /* NSFileManager_Nunja.h in Headers */,
+ 2224951411D9BAA300A4D1F2 /* Nunja.h in Headers */,
+ 2224951611D9BAA300A4D1F2 /* NunjaDelegate.h in Headers */,
+ 2224951811D9BAA300A4D1F2 /* NunjaRequest.h in Headers */,
+ 2224951A11D9BAA300A4D1F2 /* NunjaRequestHandler.h in Headers */,
+ 2224951C11D9BAA300A4D1F2 /* NunjaRequestRouter.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+ 22C53CDD11D9B242003626DF /* nunjad */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 22C53CE411D9B256003626DF /* Build configuration list for PBXNativeTarget "nunjad" */;
+ buildPhases = (
+ 22C53CDB11D9B242003626DF /* Sources */,
+ 22C53CDC11D9B242003626DF /* Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 22C53D0011D9B2F8003626DF /* PBXTargetDependency */,
+ );
+ name = nunjad;
+ productName = nunjad;
+ productReference = 22C53CDE11D9B242003626DF /* nunjad */;
+ productType = "com.apple.product-type.tool";
+ };
+ 8DC2EF4F0486A6940098B216 /* Nunja */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "Nunja" */;
+ buildPhases = (
+ 8DC2EF500486A6940098B216 /* Headers */,
+ 8DC2EF520486A6940098B216 /* Resources */,
+ 8DC2EF540486A6940098B216 /* Sources */,
+ 8DC2EF560486A6940098B216 /* Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Nunja;
+ productInstallPath = "$(HOME)/Library/Frameworks";
+ productName = Nunja;
+ productReference = 8DC2EF5B0486A6940098B216 /* Nunja.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 0867D690FE84028FC02AAC07 /* Project object */ = {
+ isa = PBXProject;
+ buildConfigurationList = 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "Nunja" */;
+ compatibilityVersion = "Xcode 3.1";
+ hasScannedForEncodings = 1;
+ mainGroup = 0867D691FE84028FC02AAC07 /* Nunja */;
+ productRefGroup = 034768DFFF38A50411DB9C8B /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 8DC2EF4F0486A6940098B216 /* Nunja */,
+ 22C53CDD11D9B242003626DF /* nunjad */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 8DC2EF520486A6940098B216 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */,
+ 22C53C3911D9B17F003626DF /* nunja.nu in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 22C53CDB11D9B242003626DF /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 22C53CE511D9B261003626DF /* nunjad.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 8DC2EF540486A6940098B216 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 2224951211D9BAA300A4D1F2 /* NSFileManager_Nunja.m in Sources */,
+ 2224951311D9BAA300A4D1F2 /* Nunja_daemonize.m in Sources */,
+ 2224951511D9BAA300A4D1F2 /* Nunja.m in Sources */,
+ 2224951711D9BAA300A4D1F2 /* NunjaDelegate.m in Sources */,
+ 2224951911D9BAA300A4D1F2 /* NunjaRequest.m in Sources */,
+ 2224951B11D9BAA300A4D1F2 /* NunjaRequestHandler.m in Sources */,
+ 2224951D11D9BAA300A4D1F2 /* NunjaRequestRouter.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 22C53D0011D9B2F8003626DF /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 8DC2EF4F0486A6940098B216 /* Nunja */;
+ targetProxy = 22C53CFF11D9B2F8003626DF /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ 089C1666FE841158C02AAC07 /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 089C1667FE841158C02AAC07 /* English */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 1DEB91AE08733DA50010E9CD /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ COPY_PHASE_STRIP = NO;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ FRAMEWORK_VERSION = A;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_ENABLE_FIX_AND_CONTINUE = YES;
+ GCC_MODEL_TUNING = G5;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = Nunja_Prefix.pch;
+ INFOPLIST_FILE = Info.plist;
+ INSTALL_PATH = "$(HOME)/Library/Frameworks";
+ PRODUCT_NAME = Nunja;
+ WRAPPER_EXTENSION = framework;
+ };
+ name = Debug;
+ };
+ 1DEB91AF08733DA50010E9CD /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ FRAMEWORK_VERSION = A;
+ GCC_MODEL_TUNING = G5;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = Nunja_Prefix.pch;
+ INFOPLIST_FILE = Info.plist;
+ INSTALL_PATH = "$(HOME)/Library/Frameworks";
+ PRODUCT_NAME = Nunja;
+ WRAPPER_EXTENSION = framework;
+ };
+ name = Release;
+ };
+ 1DEB91B208733DA50010E9CD /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = DARWIN;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ HEADER_SEARCH_PATHS = /usr/local/include;
+ ONLY_ACTIVE_ARCH = YES;
+ PREBINDING = NO;
+ SDKROOT = macosx10.6;
+ };
+ name = Debug;
+ };
+ 1DEB91B308733DA50010E9CD /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_PREPROCESSOR_DEFINITIONS = DARWIN;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ HEADER_SEARCH_PATHS = /usr/local/include;
+ PREBINDING = NO;
+ SDKROOT = macosx10.6;
+ };
+ name = Release;
+ };
+ 22C53CE011D9B242003626DF /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ COPY_PHASE_STRIP = NO;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_ENABLE_FIX_AND_CONTINUE = YES;
+ GCC_MODEL_TUNING = G5;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
+ INSTALL_PATH = /usr/local/bin;
+ OTHER_LDFLAGS = (
+ "-framework",
+ Foundation,
+ "-framework",
+ AppKit,
+ );
+ PREBINDING = NO;
+ PRODUCT_NAME = nunjad;
+ };
+ name = Debug;
+ };
+ 22C53CE111D9B242003626DF /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ COPY_PHASE_STRIP = YES;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ GCC_ENABLE_FIX_AND_CONTINUE = NO;
+ GCC_MODEL_TUNING = G5;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
+ INSTALL_PATH = /usr/local/bin;
+ OTHER_LDFLAGS = (
+ "-framework",
+ Foundation,
+ "-framework",
+ AppKit,
+ );
+ PREBINDING = NO;
+ PRODUCT_NAME = nunjad;
+ ZERO_LINK = NO;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "Nunja" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1DEB91AE08733DA50010E9CD /* Debug */,
+ 1DEB91AF08733DA50010E9CD /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "Nunja" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1DEB91B208733DA50010E9CD /* Debug */,
+ 1DEB91B308733DA50010E9CD /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 22C53CE411D9B256003626DF /* Build configuration list for PBXNativeTarget "nunjad" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 22C53CE011D9B242003626DF /* Debug */,
+ 22C53CE111D9B242003626DF /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 0867D690FE84028FC02AAC07 /* Project object */;
+}
diff --git a/Xcode/Nunja_Prefix.pch b/Xcode/Nunja_Prefix.pch
new file mode 100644
index 0000000..e69de29
diff --git a/Xcode/nunjad.m b/Xcode/nunjad.m
new file mode 100644
index 0000000..3183c90
--- /dev/null
+++ b/Xcode/nunjad.m
@@ -0,0 +1,36 @@
+#import
+#import "Nunja.h"
+#import "NunjaDelegate.h"
+#import "NunjaRequest.h"
+#import "NunjaRequestHandler.h"
+
+@interface MyNunjaDelegate : NunjaDelegate
+{
+}
+@end
+
+@implementation MyNunjaDelegate
+
+- (void) nunjaDidFinishLaunching {
+#ifdef DARWIN
+ [self addHandlerWithHTTPMethod:@"GET"
+ path:@"/block/me:"
+ block:^(NunjaRequest *REQUEST) {
+ NSMutableString *result = [NSMutableString string];
+ [result appendString:@"Handling 'block'\n"];
+ [result appendString:@"Bindings\n"];
+ [result appendString:[[REQUEST bindings] description]];
+ [result appendString:@"\n"];
+ [result appendString:@"Query\n"];
+ [result appendString:[[REQUEST query] description]];
+ [REQUEST setContentType:@"text/plain"];
+ return result;
+ }];
+#endif
+}
+@end
+
+int main (int argc, const char * argv[])
+{
+ return NunjaMain(argc, argv, @"MyNunjaDelegate");
+}
diff --git a/nu/markup.nu b/nu/markup.nu
deleted file mode 100644
index 323cf46..0000000
--- a/nu/markup.nu
+++ /dev/null
@@ -1,56 +0,0 @@
-;; requires Nunja
-(load "Nunja")
-
-(set &html (NunjaMarkupOperator operatorWithTag:"html" prefix:<<-END
-
-END))
-
-(global XMLNS "http://www.w3.org/1999/xhtml")
-
-(macro markup (*names)
- `(progn
- (',*names each:
- (do (name)
- (set stringName (name stringValue))
- (set expression
- (list 'global ((+ "&" stringName) symbolValue) '(NunjaMarkupOperator operatorWithTag:stringName)))
- (eval expression)))))
-
-# add tags as needed
-(markup a
- body
- br
- button
- div
- fieldset
- form
- head
- h1
- h2
- h3
- h4
- h5
- h6
- img
- input
- label
- li
- link
- meta
- ol
- option
- p
- pre
- script
- select
- span
- strong
- style
- table
- td
- textarea
- th
- title
- tr
- tbody
- ul)
diff --git a/nu/nunja.nu b/nu/nunja.nu
index f5728c5..23cd2af 100644
--- a/nu/nunja.nu
+++ b/nu/nunja.nu
@@ -14,19 +14,12 @@
;; See the License for the specific language governing permissions and
;; limitations under the License.
-;; import some useful C functions
-(global random (NuBridgedFunction functionWithName:"random" signature:"l"))
-(global srandom (NuBridgedFunction functionWithName:"srandom" signature:"vI"))
-
(case (set SYSTEM ((NSString stringWithShellCommand:"uname") chomp))
("Darwin" (import Foundation))
("Linux" (global NSLog (NuBridgedFunction functionWithName:"NSLog" signature:"v@"))
(global NSUTF8StringEncoding 4))
(else nil))
-(load "Nu:template")
-(load "Nunja:mime")
-
;; @class NSDate
;; @discussion Extensions for Nunja.
(class NSDate
@@ -39,13 +32,13 @@
timeZone:(NSTimeZone localTimeZone) locale:nil))
(result appendString:((NSTimeZone localTimeZone) abbreviation))
result)
-
+
;; Get an RFC822-compliant representation of a date, expressed in GMT.
(- (id) rfc822-GMT is
(set result ((NSMutableString alloc) init))
(result appendString:
(self descriptionWithCalendarFormat:"%a, %d %b %Y %H:%M:%S GMT"
- timeZone:(NSTimeZone timeZoneWithName:"GMT") locale:nil))
+ timeZone:(NSTimeZone timeZoneWithName:"GMT") locale:nil))
result)
;; Get an RFC1123-compliant representation of a date.
@@ -66,301 +59,83 @@
(result insertString:":" atIndex:(- (result length) 2))
result))
-;; use this pattern to extract a cookie from a header
-(set cookie-pattern (regex -"[ ]*([^=]*)=(.*)"))
-;; @class NunjaCookie
-;; @discussion A class for managing user-identifying cookies.
-(class NunjaCookie is NSObject
- (ivars)
- (ivar-accessors)
-
- ;; Generate a random identifier for use in a cookie.
- (+ (id) randomIdentifier is
- "#{((random) stringValue)}#{((random) stringValue)}#{((random) stringValue)}#{((random) stringValue)}")
-
- ;; Construct a cookie for a specified user.
- (+ (id) cookieForUser:(id) user is
- ((self alloc) initWithUser:user
- value:(self randomIdentifier)
- expiration:(NSDate dateWithTimeIntervalSinceNow:3600)))
-
- ;; Initialize a cookie for a specified user.
- (- (id) initWithUser:(id) user value:(id) value expiration:(id) expiration is
- (super init)
- (set @name "session")
- (set @user user)
- (set @value value)
- (set @expiration expiration)
- (set @stringValue nil)
- self)
-
- ;; Get a string description of a cookie.
- (- (id) description is
- "cookie=#{@name} value=#{@value} user=#{@user} expiration=#{(@expiration rfc822)}")
-
- ;; Get a string value for a cookie suitable for inclusion in a response header.
- (- (id) stringValue is "#{@name}=#{@value}; Expires:#{(@expiration rfc1123)}; Path=/"))
-
-;; @class NunjaRequest
-;; @discussion A class for managing requests received by the server.
-(class NunjaRequest
- (ivar-accessors)
-
- (- (id) cookies is
- (unless @_cookies
- (set @_cookies
- (if (set cookies ((self requestHeaders) objectForKey:"Cookie"))
- (then (set cookieDictionary (dict))
- ((cookies componentsSeparatedByString:";") each:
- (do (cookieDescription)
- (if (set match (cookie-pattern findInString:cookieDescription))
- (cookieDictionary setObject:(match groupAtIndex:2)
- forKey:(match groupAtIndex:1)))))
- cookieDictionary)
- (else (dict)))))
- @_cookies)
-
- (- (id) post is
- (if (Nunja verbose)
- (puts "body is")
- (puts ((NSString alloc) initWithData:(self body) encoding:NSUTF8StringEncoding)))
- (set d (((NSString alloc) initWithData:(self body) encoding:NSUTF8StringEncoding) urlQueryDictionary))
- (if (Nunja verbose)
- (puts (d description)))
- d)
-
- (- (void) setContentType:(id)t is (self setValue:t forResponseHeader:"Content-Type"))
-
- (- (void) redirectToLocation:(id) location is
- (self setValue:location forResponseHeader:"Location")
- (self respondWithCode:303 message:"redirecting" string:"redirecting")))
-
-;; An HTTP request handler. Handlers consist of an action, a pattern, and a block.
-;; The action is an HTTP verb such as "get" or "post", the pattern is either an NSString
-;; or a NuRegex, and the block is a NuBlock to be evaluated in the request handling.
-;; Request handers are typically created using the "get" or "post" macros and are responsible
-;; for setting the response headers and returning the appropriate response data, which can be
-;; either raw data (in an NSData object) or a string containing HTML text.
-(class NunjaRequestHandler is NSObject
- (ivar (id) action (id) pattern (id) block (id) keys)
- (ivars)
- (ivar-accessors)
-
- (- (void) setValue:(id) value forKey:(id) key is
- (puts "this should not get called")
- (puts "#{key}: #{value}"))
-
- ;; Create a handler with a specified action, pattern, and block. Used internally.
- (+ (id) handlerWithAction:(id)action pattern:(id)pattern block:(id)block is
- ;; if the pattern is a string that has dynamic parts, turn it into a regex
- (set keys nil)
- (if (pattern isKindOfClass:NSString)
- (set dynamic-part-regex (regex -"/:([^/]*)"))
- (set tokens (dynamic-part-regex findAllInString:pattern))
- (if (tokens count)
- (set keys (tokens map:(do (token) (token groupAtIndex:1))))
- (set newpattern (+ "^" (dynamic-part-regex replaceWithString:-"/([^/]*)" inString:pattern) "$"))
- (set pattern (regex newpattern))))
-
- (set handler ((self alloc) init))
- (handler setAction:action)
- (handler setPattern:pattern)
- (handler setKeys:keys)
- (handler setBlock:block)
- handler)
-
- ;; Try to match the handler against a specified action and path. Used internally.
- (- (id)matchRequest:(id)request is
- (if (or (eq (request command) @action)
- (and (eq (request command) "HEAD") (eq @action "GET")))
- (then
- (set path (request path))
- (cond ;; match against a string
- ((@pattern isKindOfClass:NSString)
- (eq @pattern path))
- ;; match against a regular expression
- ((@pattern isKindOfClass:NuRegex)
- (set match (@pattern findInString:path))
- (if match
- (then
- (request setMatch:match)
- (if (and @keys (eq (+ (@keys count) 1) (match count)))
- (set bindings (dict))
- ((@keys count) times:
- (do (i)
- (bindings setValue:(match groupAtIndex:(+ i 1))
- forKey:(@keys objectAtIndex:i))))
- (request setBindings:bindings))
- YES)
- (else nil)))
- ;; unsupported pattern type, no match
- (else nil)))
- ;; unsupported action, no match
- (else nil)))
-
- (set text-html-pattern (regex "^text/html.*$"))
-
- ;; Handle a request. Used internally.
- (- (id)handleRequest:(id)request is
- (if (Nunja verbose)
- (puts "handling request #{(request uri)}")
- (puts "request from host #{(request remoteHost)} port #{(request remotePort)}"))
- (let (body (@block request))
- (cond
- ;; return without responding, this means the handler has rejected the URL
- ((not body) nil)
- ;; return data objects as-is
- ((body isKindOfClass:NSData) ;; we should set the content type if it isn't set
- (request respondWithData:body)
- t)
- ;; return other non-strings as their stringValues
- ((not (body isKindOfClass:NSString))
- (request respondWithString:(body stringValue))
- t)
- ;; if a content type is set and it isn't text/html, return string as-is
- ((and (set content-type (request valueForResponseHeader:"Content-Type"))
- (not (text-html-pattern findInString:content-type)))
- (request respondWithString:body)
- t)
- ;; return string as html
- (else (request setContentType:"text/html; charset=UTF-8")
- (request respondWithString:body)
- t))))
-
- ;; Return a response redirecting the client to a new location. This method may be called from action handlers.
- (- (id)redirectResponse:(id)request toLocation:(id)location is
- (request setValue:location forResponseHeader:"Location")
- (request respondWithCode:303 message:"redirecting" string:"redirecting")))
-
-(class Nunja
- ;; Return a response redirecting the client to a new location. This method may be called from action handlers.
- (+ (id)redirectResponse:(id)request toLocation:(id)location is
- (request setValue:location forResponseHeader:"Location")
- (request respondWithCode:303 message:"redirecting" string:"redirecting")))
-
-(class NunjaCache is NSObject
- (ivar (id) cache)
-
- (set sharedCache nil) ;; closure makes this a class variable
-
- (+ (id) sharedCache is
- (unless sharedCache (set sharedCache ((self alloc) init)))
- sharedCache)
-
- (- (id) init is (super init) (set @cache (dict)) self)
-
- (- (void) setResponse:(id) response forPath:(id)path is
- (puts "caching response for #{path}")
- (@cache setObject:response forKey:path))
-
- (- (id) handleRequest:(id) request is
- (set path (request path))
- (if (set item (@cache objectForKey:path))
- (then (request respondWithString:item)
- t)
- (else nil))))
-
-;; @class NunjaController
-;; @discussion The Nunja Controller. Responsible for handling requests.
-(class NunjaController is NSObject
- (ivar (id) handlers (id) root (id) defaultHandler)
- (ivar-accessors)
-
- (set privateSharedController nil) ;; private shared variable
-
- (+ (id) sharedController is
- privateSharedController)
-
- (- (id) initWithSite:(id) site is
- (self init)
- (set @handlers (array))
- (set privateSharedController self)
- (set @root site)
- (load (+ site "/site.nu"))
- self)
-
- (- (void) handleRequest:(id) request is
- (set path (request path))
- (if (Nunja verbose)
- (puts (+ "REQUEST " (request command) " " path "-----"))
- (puts ((request requestHeaders) description)))
- (request setValue:"Nunja" forResponseHeader:"Server")
-
- (set handled nil)
-
- (set handled ((NunjaCache sharedCache) handleRequest:request))
-
- (unless handled ;; try using the programmed handlers
- (@handlers each:
- (do (handler)
- (if (and (not handled) (handler matchRequest:request))
- (set handled (handler handleRequest:request))))))
-
- (unless handled ;; does the path end in a '/'? If so, append index.html
- (set lastCharacter (path characterAtIndex:(- (path length) 1)))
- (if (eq lastCharacter '/')
- (set filename (+ @root "/public" path "index.html"))
- (if ((NSFileManager defaultManager) fileExistsAtPath:filename)
- (set data (NSData dataWithContentsOfFile:filename))
- (request setValue:(mime-type filename) forResponseHeader:"Content-Type")
- (request setValue:"max-age=3600" forResponseHeader:"Cache-Control")
- (request respondWithData:data)
- (set handled YES))))
-
- (unless handled ;; look for a file or directory that matches the path
- (set filename (+ @root "/public" path))
- (if ((NSFileManager defaultManager) fileExistsAtPath:filename)
- (then
- (if ((NSFileManager defaultManager) directoryExistsAtPath:filename)
- (then ;; for a directory, redirect to the same path with '/' appended
- (request setValue:(+ path "/") forResponseHeader:"Location")
- (request respondWithCode:301 message:"moved permanently" string:"Moved Permanently")
- (set handled YES))
- (else ;; for a file, send its contents
- (set data (NSData dataWithContentsOfFile:filename))
- (request setValue:(mime-type filename) forResponseHeader:"Content-Type")
- (request setValue:"max-age=3600" forResponseHeader:"Cache-Control")
- (request respondWithData:data)
- (set handled YES))))))
-
- (unless handled ;; try appending .html to the path
- (set filename (+ @root "/public" path ".html"))
- (if ((NSFileManager defaultManager) fileExistsAtPath:filename)
- (set data (NSData dataWithContentsOfFile:filename))
- (request setValue:"text/html" forResponseHeader:"Content-Type")
- (request setValue:"max-age=3600" forResponseHeader:"Cache-Control")
- (request respondWithData:data)
- (set handled YES)))
-
- (unless handled
- (if @defaultHandler
- (then (@defaultHandler handleRequest:request))
- (else (request respondWithCode:404 message:"Not Found" string:"Not Found. You said: #{(request command)} #{(request path)}"))))))
;; Declare a get action.
-(macro-1 get (pattern *body)
- `(((NunjaController sharedController) handlers)
- << (NunjaRequestHandler handlerWithAction:"GET"
- pattern:,pattern
- block:(do (REQUEST) ,@*body))))
+(global get (macro get (path *body)
+ `((NunjaDelegate sharedDelegate)
+ addHandlerWithHTTPMethod:"GET"
+ path:,path
+ block:(do (REQUEST) ,@*body))))
;; Declare a post action.
-(macro-1 post (pattern *body)
- `(((NunjaController sharedController) handlers)
- << (NunjaRequestHandler handlerWithAction:"POST"
- pattern:,pattern
- block:(do (REQUEST) ,@*body))))
+(global post (macro post (path *body)
+ `((NunjaDelegate sharedDelegate)
+ addHandlerWithHTTPMethod:"POST"
+ path:,path
+ block:(do (REQUEST) ,@*body))))
;; Declare a 404 handler.
-(macro-1 get-404 (*body)
- `((NunjaController sharedController) setDefaultHandler:
- (NunjaRequestHandler handlerWithAction:"GET"
- pattern:nil
- block:(do (REQUEST) ,@*body))))
-
-;; Set the top-level directory for a site
-(macro-1 root (top-level-directory)
- `((NunjaController sharedController) setRoot:,top-level-directory))
-
-
+(global get-404 (macro get-404 (*body)
+ `((NunjaDelegate sharedDelegate)
+ setDefaultHandlerWithBlock:(do (REQUEST) ,@*body))))
+
+(Nunja setMimeTypes:
+ (dict "ai" "application/postscript"
+ "asc" "text/plain"
+ "avi" "video/x-msvideo"
+ "bin" "application/octet-stream"
+ "bmp" "image/bmp"
+ "class" "application/octet-stream"
+ "cer" "application/pkix-cert"
+ "crl" "application/pkix-crl"
+ "crt" "application/x-x509-ca-cert"
+ "css" "text/css"
+ "dms" "application/octet-stream"
+ "doc" "application/msword"
+ "dvi" "application/x-dvi"
+ "eps" "application/postscript"
+ "etx" "text/x-setext"
+ "exe" "application/octet-stream"
+ "gif" "image/gif"
+ "htm" "text/html"
+ "html" "text/html"
+ "ico" "application/icon"
+ "ics" "text/calendar"
+ "jpe" "image/jpeg"
+ "jpeg" "image/jpeg"
+ "jpg" "image/jpeg"
+ "js" "text/javascript"
+ "lha" "application/octet-stream"
+ "lzh" "application/octet-stream"
+ "mobileconfig" "application/x-apple-aspen-config"
+ "mov" "video/quicktime"
+ "mpe" "video/mpeg"
+ "mpeg" "video/mpeg"
+ "mpg" "video/mpeg"
+ "m3u8" "application/x-mpegURL"
+ "pbm" "image/x-portable-bitmap"
+ "pdf" "application/pdf"
+ "pgm" "image/x-portable-graymap"
+ "png" "image/png"
+ "pnm" "image/x-portable-anymap"
+ "ppm" "image/x-portable-pixmap"
+ "ppt" "application/vnd.ms-powerpoint"
+ "ps" "application/postscript"
+ "qt" "video/quicktime"
+ "ras" "image/x-cmu-raster"
+ "rb" "text/plain"
+ "rd" "text/plain"
+ "rtf" "application/rtf"
+ "sgm" "text/sgml"
+ "sgml" "text/sgml"
+ "tif" "image/tiff"
+ "tiff" "image/tiff"
+ "ts" "video/MP2T"
+ "txt" "text/plain"
+ "xbm" "image/x-xbitmap"
+ "xls" "application/vnd.ms-excel"
+ "xml" "text/xml"
+ "xpm" "image/x-xpixmap"
+ "xwd" "image/x-xwindowdump"
+ "zip" "application/zip"))
diff --git a/nunjad.nu b/nunjad
similarity index 90%
rename from nunjad.nu
rename to nunjad
index 102d5da..5cacfbd 100755
--- a/nunjad.nu
+++ b/nunjad
@@ -17,8 +17,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-(load "NuHTTPHelpers")
(load "Nunja")
+(load "NuHTTPHelpers")
(set exit (NuBridgedFunction functionWithName:"exit" signature:"vi"))
@@ -41,6 +41,7 @@
;; the option(s) we need to set
(set site nil)
(set port 3000)
+(set localOnly NO)
;; process the remaining arguments
(while (< argi (argv count))
@@ -49,22 +50,23 @@
("--site" (set argi (+ argi 1)) (set site (argv argi)))
("-p" (set argi (+ argi 1)) (set port ((argv argi) intValue)))
("--port" (set argi (+ argi 1)) (set port ((argv argi) intValue)))
- ("-l" (Nunja setLocalOnly:YES))
- ("--local" (Nunja setLocalOnly:YES))
+ ("-l" (set localOnly YES))
+ ("--local" (set localOnly YES))
("-v" (Nunja setVerbose:YES))
("--verbose" (Nunja setVerbose:YES))
(else (puts (+ "unknown option: " (argv argi)))
(exit -1)))
(set argi (+ argi 1)))
-(set n ((Nunja alloc) init))
-(if (Nunja localOnly)
+(set n (Nunja nunja))
+(if localOnly
(then (n bindToAddress:"127.0.0.1" port:port))
(else (n bindToAddress:"0.0.0.0" port:port)))
(if site
- (n setController:((NunjaController alloc) initWithSite:site)))
+ (n setDelegate:((NunjaDelegate alloc) initWithSite:site)))
(puts (+ "Nunja is running on port " port))
(set $site site) ;; make the path to the site directory available to handlers
+((n delegate) dump)
(n run)
diff --git a/nunjad/nunjad b/nunjad/nunjad
deleted file mode 100755
index 1df5bde..0000000
Binary files a/nunjad/nunjad and /dev/null differ
diff --git a/nunjad/nunjad.1 b/nunjad/nunjad.1
deleted file mode 100644
index 2967904..0000000
--- a/nunjad/nunjad.1
+++ /dev/null
@@ -1,79 +0,0 @@
-.\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples.
-.\"See Also:
-.\"man mdoc.samples for a complete listing of options
-.\"man mdoc for the short list of editing options
-.\"/usr/share/misc/mdoc.template
-.Dd 6/23/10 \" DATE
-.Dt nunjad 1 \" Program name and manual section number
-.Os Darwin
-.Sh NAME \" Section Header - required - don't modify
-.Nm nunjad,
-.\" The following lines are read in generating the apropos(man -k) database. Use only key
-.\" words here as the database is built based on the words here and in the .ND line.
-.Nm Other_name_for_same_program(),
-.Nm Yet another name for the same program.
-.\" Use .Nm macro to designate other names for the documented program.
-.Nd This line parsed for whatis database.
-.Sh SYNOPSIS \" Section Header - required - don't modify
-.Nm
-.Op Fl abcd \" [-abcd]
-.Op Fl a Ar path \" [-a path]
-.Op Ar file \" [file]
-.Op Ar \" [file ...]
-.Ar arg0 \" Underlined argument - use .Ar anywhere to underline
-arg2 ... \" Arguments
-.Sh DESCRIPTION \" Section Header - required - don't modify
-Use the .Nm macro to refer to your program throughout the man page like such:
-.Nm
-Underlining is accomplished with the .Ar macro like this:
-.Ar underlined text .
-.Pp \" Inserts a space
-A list of items with descriptions:
-.Bl -tag -width -indent \" Begins a tagged list
-.It item a \" Each item preceded by .It macro
-Description of item a
-.It item b
-Description of item b
-.El \" Ends the list
-.Pp
-A list of flags and their descriptions:
-.Bl -tag -width -indent \" Differs from above in tag removed
-.It Fl a \"-a flag as a list item
-Description of -a flag
-.It Fl b
-Description of -b flag
-.El \" Ends the list
-.Pp
-.\" .Sh ENVIRONMENT \" May not be needed
-.\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1
-.\" .It Ev ENV_VAR_1
-.\" Description of ENV_VAR_1
-.\" .It Ev ENV_VAR_2
-.\" Description of ENV_VAR_2
-.\" .El
-.Sh FILES \" File used or created by the topic of the man page
-.Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact
-.It Pa /usr/share/file_name
-FILE_1 description
-.It Pa /Users/joeuser/Library/really_long_file_name
-FILE_2 description
-.El \" Ends the list
-.\" .Sh DIAGNOSTICS \" May not be needed
-.\" .Bl -diag
-.\" .It Diagnostic Tag
-.\" Diagnostic informtion here.
-.\" .It Diagnostic Tag
-.\" Diagnostic informtion here.
-.\" .El
-.Sh SEE ALSO
-.\" List links in ascending order by section, alphabetically within a section.
-.\" Please do not reference files that do not exist without filing a bug report
-.Xr a 1 ,
-.Xr b 1 ,
-.Xr c 1 ,
-.Xr a 2 ,
-.Xr b 2 ,
-.Xr a 3 ,
-.Xr b 3
-.\" .Sh BUGS \" Document known, unremedied bugs
-.\" .Sh HISTORY \" Document history if command behaves in a unique manner
diff --git a/nunjad/nunjad.m b/nunjad/nunjad.m
deleted file mode 100644
index a88b790..0000000
--- a/nunjad/nunjad.m
+++ /dev/null
@@ -1,61 +0,0 @@
-#import
-
-@protocol NunjaProtocol
-- (int) bindToAddress:(const char *) address port:(int) port;
-- (BOOL) localOnly;
-- (void) setLocalOnly:(BOOL) l;
-- (void) setVerbose:(BOOL) v;
-- (void) setController:(id) controller;
-- (void) run;
-@end
-
-int main (int argc, const char * argv[]) {
- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
-
- Class Nunja = NSClassFromString(@"Nunja");
- Class NunjaController = NSClassFromString(@"NunjaController");
-
- int port = 5000;
- NSString *site = nil;
-
- int i = 0;
- while (i < argc) {
- if (!strcmp(argv[i], "-s") ||
- !strcmp(argv[i], "--site")) {
- if (++i < argc) {
- site = [[[NSString alloc] initWithCString:argv[i]] autorelease];
- }
- }
- else if (!strcmp(argv[i], "-p") ||
- !strcmp(argv[i], "--port")) {
- if (++i < argc) {
- port = atoi(argv[i]);
- }
- }
- else if (!strcmp(argv[i], "-l") ||
- !strcmp(argv[i], "--local")) {
- [Nunja setLocalOnly:YES];
- }
- else if (!strcmp(argv[i], "-v") ||
- !strcmp(argv[i], "--verbose")) {
- [Nunja setVerbose:YES];
- }
- i++;
- }
-
- if (!site) {
- NSLog(@"Please specify a site description with the -s or --site option");
- } else {
- id nunja = [[Nunja alloc] init];
- if ([Nunja localOnly]) {
- [nunja bindToAddress:"127.0.0.1" port:port];
- } else {
- [nunja bindToAddress:"0.0.0.0" port:port];
- }
- [nunja setController:[[NunjaController alloc] initWithSite:site]];
- [nunja run];
- }
-
- [pool drain];
- return 0;
-}
diff --git a/nunjad/nunjad.xcodeproj/project.pbxproj b/nunjad/nunjad.xcodeproj/project.pbxproj
deleted file mode 100644
index ac94c49..0000000
--- a/nunjad/nunjad.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,225 +0,0 @@
-// !$*UTF8*$!
-{
- archiveVersion = 1;
- classes = {
- };
- objectVersion = 45;
- objects = {
-
-/* Begin PBXBuildFile section */
- 221A336B11D286E900535B31 /* Nu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 221A336A11D286E900535B31 /* Nu.framework */; };
- 221A336F11D2870400535B31 /* Nunja.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 221A336E11D2870400535B31 /* Nunja.framework */; };
- 8DD76F9A0486AA7600D96B5E /* nunjad.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* nunjad.m */; settings = {ATTRIBUTES = (); }; };
- 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; };
- 8DD76F9F0486AA7600D96B5E /* nunjad.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = C6859EA3029092ED04C91782 /* nunjad.1 */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXCopyFilesBuildPhase section */
- 8DD76F9E0486AA7600D96B5E /* CopyFiles */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 8;
- dstPath = /usr/share/man/man1/;
- dstSubfolderSpec = 0;
- files = (
- 8DD76F9F0486AA7600D96B5E /* nunjad.1 in CopyFiles */,
- );
- runOnlyForDeploymentPostprocessing = 1;
- };
-/* End PBXCopyFilesBuildPhase section */
-
-/* Begin PBXFileReference section */
- 08FB7796FE84155DC02AAC07 /* nunjad.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = nunjad.m; sourceTree = ""; };
- 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; };
- 221A336A11D286E900535B31 /* Nu.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nu.framework; path = /Library/Frameworks/Nu.framework; sourceTree = ""; };
- 221A336E11D2870400535B31 /* Nunja.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nunja.framework; path = Library/Frameworks/Nunja.framework; sourceTree = SDKROOT; };
- 8DD76FA10486AA7600D96B5E /* nunjad */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = nunjad; sourceTree = BUILT_PRODUCTS_DIR; };
- C6859EA3029092ED04C91782 /* nunjad.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = nunjad.1; sourceTree = ""; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
- 8DD76F9B0486AA7600D96B5E /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */,
- 221A336B11D286E900535B31 /* Nu.framework in Frameworks */,
- 221A336F11D2870400535B31 /* Nunja.framework in Frameworks */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
- 08FB7794FE84155DC02AAC07 /* nunjad */ = {
- isa = PBXGroup;
- children = (
- 08FB7795FE84155DC02AAC07 /* Source */,
- C6859EA2029092E104C91782 /* Documentation */,
- 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */,
- 1AB674ADFE9D54B511CA2CBB /* Products */,
- );
- name = nunjad;
- sourceTree = "";
- };
- 08FB7795FE84155DC02AAC07 /* Source */ = {
- isa = PBXGroup;
- children = (
- 08FB7796FE84155DC02AAC07 /* nunjad.m */,
- );
- name = Source;
- sourceTree = "";
- };
- 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */ = {
- isa = PBXGroup;
- children = (
- 08FB779EFE84155DC02AAC07 /* Foundation.framework */,
- 221A336A11D286E900535B31 /* Nu.framework */,
- 221A336E11D2870400535B31 /* Nunja.framework */,
- );
- name = "External Frameworks and Libraries";
- sourceTree = "";
- };
- 1AB674ADFE9D54B511CA2CBB /* Products */ = {
- isa = PBXGroup;
- children = (
- 8DD76FA10486AA7600D96B5E /* nunjad */,
- );
- name = Products;
- sourceTree = "";
- };
- C6859EA2029092E104C91782 /* Documentation */ = {
- isa = PBXGroup;
- children = (
- C6859EA3029092ED04C91782 /* nunjad.1 */,
- );
- name = Documentation;
- sourceTree = "";
- };
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
- 8DD76F960486AA7600D96B5E /* nunjad */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "nunjad" */;
- buildPhases = (
- 8DD76F990486AA7600D96B5E /* Sources */,
- 8DD76F9B0486AA7600D96B5E /* Frameworks */,
- 8DD76F9E0486AA7600D96B5E /* CopyFiles */,
- );
- buildRules = (
- );
- dependencies = (
- );
- name = nunjad;
- productInstallPath = "$(HOME)/bin";
- productName = nunjad;
- productReference = 8DD76FA10486AA7600D96B5E /* nunjad */;
- productType = "com.apple.product-type.tool";
- };
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
- 08FB7793FE84155DC02AAC07 /* Project object */ = {
- isa = PBXProject;
- buildConfigurationList = 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "nunjad" */;
- compatibilityVersion = "Xcode 3.1";
- hasScannedForEncodings = 1;
- mainGroup = 08FB7794FE84155DC02AAC07 /* nunjad */;
- projectDirPath = "";
- projectRoot = "";
- targets = (
- 8DD76F960486AA7600D96B5E /* nunjad */,
- );
- };
-/* End PBXProject section */
-
-/* Begin PBXSourcesBuildPhase section */
- 8DD76F990486AA7600D96B5E /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 8DD76F9A0486AA7600D96B5E /* nunjad.m in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXSourcesBuildPhase section */
-
-/* Begin XCBuildConfiguration section */
- 1DEB927508733DD40010E9CD /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- COPY_PHASE_STRIP = NO;
- GCC_DYNAMIC_NO_PIC = NO;
- GCC_ENABLE_FIX_AND_CONTINUE = YES;
- GCC_MODEL_TUNING = G5;
- GCC_OPTIMIZATION_LEVEL = 0;
- GCC_PRECOMPILE_PREFIX_HEADER = YES;
- INSTALL_PATH = /usr/local/bin;
- PRODUCT_NAME = nunjad;
- };
- name = Debug;
- };
- 1DEB927608733DD40010E9CD /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- GCC_MODEL_TUNING = G5;
- GCC_PRECOMPILE_PREFIX_HEADER = YES;
- INSTALL_PATH = /usr/local/bin;
- PRODUCT_NAME = nunjad;
- };
- name = Release;
- };
- 1DEB927908733DD40010E9CD /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_OPTIMIZATION_LEVEL = 0;
- GCC_WARN_ABOUT_RETURN_TYPE = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- ONLY_ACTIVE_ARCH = YES;
- PREBINDING = NO;
- SDKROOT = macosx10.6;
- };
- name = Debug;
- };
- 1DEB927A08733DD40010E9CD /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_WARN_ABOUT_RETURN_TYPE = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- PREBINDING = NO;
- SDKROOT = macosx10.6;
- };
- name = Release;
- };
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
- 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "nunjad" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 1DEB927508733DD40010E9CD /* Debug */,
- 1DEB927608733DD40010E9CD /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "nunjad" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 1DEB927908733DD40010E9CD /* Debug */,
- 1DEB927A08733DD40010E9CD /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
-/* End XCConfigurationList section */
- };
- rootObject = 08FB7793FE84155DC02AAC07 /* Project object */;
-}
diff --git a/objc/NSFileManager_Nunja.h b/objc/NSFileManager_Nunja.h
new file mode 100644
index 0000000..8924419
--- /dev/null
+++ b/objc/NSFileManager_Nunja.h
@@ -0,0 +1,23 @@
+/*!
+@file NSFileManager_Nunja.h
+@discussion General utilities for the Nunja web server.
+@copyright Copyright (c) 2010 Neon Design Technology, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+#import
+
+@interface NSFileManager (Nunja)
+- (BOOL) directoryExistsAtPath:(NSString *) path;
+@end
diff --git a/objc/NSFileManager_Nunja.m b/objc/NSFileManager_Nunja.m
new file mode 100644
index 0000000..849165a
--- /dev/null
+++ b/objc/NSFileManager_Nunja.m
@@ -0,0 +1,33 @@
+/*!
+@file NSFileManager_Nunja.m
+@discussion General utilities for the Nunja web server.
+@copyright Copyright (c) 2010 Neon Design Technology, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+#import "NSFileManager_Nunja.h"
+
+@implementation NSFileManager (Nunja)
+
+- (BOOL) directoryExistsAtPath:(NSString *) path
+{
+ BOOL isDirectory = NO;
+ BOOL fileExists = [self fileExistsAtPath:path isDirectory:&isDirectory];
+ if (!fileExists)
+ return NO;
+ else
+ return isDirectory;
+}
+
+@end
diff --git a/objc/Nunja.h b/objc/Nunja.h
new file mode 100644
index 0000000..37c84ef
--- /dev/null
+++ b/objc/Nunja.h
@@ -0,0 +1,66 @@
+/*!
+ @file Nunja.h
+ @discussion Core of the Nunja web server.
+ @copyright Copyright (c) 2010 Neon Design Technology, Inc.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#import
+#import
+
+@class NunjaRequest;
+
+@protocol NunjaDelegateProtocol
+// Override this to perform Objective-C setup of your Nunja.
+- (void) nunjaDidFinishLaunching;
+
+// Call this within nunjaDidFinishLaunching to add a handler.
+- (void) addHandlerWithHTTPMethod:(NSString *)httpMethod path:(NSString *)path block:(id)block;
+
+// Call this within nunjaDidFinishLaunching to set the 404 handler.
+- (void) setDefaultHandlerWithBlock:(id) block;
+
+// Override this to add your own custom request processing. You probably won't need this.
+- (void) handleRequest:(NunjaRequest *)request;
+@end
+
+
+@interface Nunja : NSObject {}
+// Get a Nunja instance. We only support one per process.
++ (Nunja *) nunja;
+
+// Control logging
++ (void) setVerbose:(BOOL) v;
++ (BOOL) verbose;
+
+// Known MIME types
++ (NSMutableDictionary *) mimeTypes;
++ (void) setMimeTypes:(NSMutableDictionary *) dictionary;
++ (NSString *) mimeTypeForFileWithName:(NSString *) filename;
+
+// The delegate performs all request handling.
+- (void) setDelegate:(id) d;
+- (id) delegate;
+
+// Bind the server to a specified address and port.
+- (int) bindToAddress:(NSString *) address port:(int) port;
+
+// Run the server.
+- (void) run;
+
+@end
+
+
+// Run Nunja. Pass nil for NunjaDelegateClassName to set up your site with Nu (site.nu).
+int NunjaMain(int argc, const char *argv[], NSString *NunjaDelegateClassName);
diff --git a/objc/Nunja.m b/objc/Nunja.m
new file mode 100644
index 0000000..24f99a5
--- /dev/null
+++ b/objc/Nunja.m
@@ -0,0 +1,363 @@
+/*!
+ @file Nunja.m
+ @discussion Core of the Nunja web server.
+ @copyright Copyright (c) 2008 Neon Design Technology, Inc.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#define HTTP_SEEOTHER 303
+#define HTTP_DENIED 403
+
+#include
+#include
+#include // inet_ntoa
+
+#import
+#import
+
+#import "Nunja.h"
+#import "NunjaRequest.h"
+#import "NunjaDelegate.h"
+
+void NunjaInit()
+{
+ static int initialized = 0;
+ if (!initialized) {
+ initialized = 1;
+ [Nu loadNuFile:@"nunja" fromBundleWithIdentifier:@"nu.programming.nunja" withContext:nil];
+ }
+}
+
+BOOL verbose_nunja = NO;
+
+@interface ConcreteNunja : Nunja
+{
+ struct event_base *event_base;
+ struct evhttp *httpd;
+ id delegate;
+}
+
+- (id) delegate;
+@end
+
+@implementation ConcreteNunja
+
+
++ (void) load
+{
+ NunjaInit();
+}
+
+static void nunja_request_handler(struct evhttp_request *req, void *nunja_pointer)
+{
+ Nunja *nunja = (Nunja *) nunja_pointer;
+ id delegate = [nunja delegate];
+ if (delegate) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NunjaRequest *request = [[NunjaRequest alloc] initWithNunja:nunja request:req];
+ [delegate handleRequest:request];
+ [request release];
+ [pool release];
+ }
+ else {
+ nunja_response_helper(req, HTTP_OK, @"OK",
+ [[NSString stringWithFormat:@"Please set the Nunja delegate.
If you are running nunjad, use the '-s' option to specify a site.
\nRequest: %s\n",
+ evhttp_request_uri(req)]
+ dataUsingEncoding:NSUTF8StringEncoding]);
+ }
+}
+
+- (id) init
+{
+ [super init];
+ event_base = event_init();
+ evdns_init();
+ httpd = evhttp_new(event_base);
+ evhttp_set_gencb(httpd, nunja_request_handler, self);
+ delegate = nil;
+ return self;
+}
+
+- (int) bindToAddress:(NSString *) address port:(int) port
+{
+ return evhttp_bind_socket(httpd, [address cStringUsingEncoding:NSUTF8StringEncoding], port);
+}
+
+- (void) run
+{
+ event_base_dispatch(event_base);
+}
+
+- (void) dealloc
+{
+ evhttp_free(httpd);
+ [super dealloc];
+}
+
+- (id) delegate
+{
+ return delegate;
+}
+
+- (void) setDelegate:(id) d
+{
+ [d retain];
+ [delegate release];
+ delegate = d;
+}
+
+@class NuBlock;
+@class NuCell;
+
+static void nunja_dns_gethostbyname_cb(int result, char type, int count, int ttl, void *addresses, void *arg)
+{
+ id address = nil;
+ if (result == DNS_ERR_TIMEOUT) {
+ fprintf(stdout, "[Timed out] ");
+ }
+ else if (result != DNS_ERR_NONE) {
+ fprintf(stdout, "[Error code %d] ", result);
+ }
+ else {
+ fprintf(stdout, "type: %d, count: %d, ttl: %d\n", type, count, ttl);
+ switch (type) {
+ case DNS_IPv4_A:
+ {
+ struct in_addr *in_addrs = addresses;
+ if (ttl < 0) {
+ // invalid resolution
+ }
+ else if (count == 0) {
+ // no addresses
+ }
+ else {
+ address = [NSString stringWithFormat:@"%s", inet_ntoa(in_addrs[0])];
+ }
+ break;
+ }
+ case DNS_PTR:
+ /* may get at most one PTR */
+ // this needs review. TB.
+ if (count == 1)
+ fprintf(stdout, "addresses: %s ", *(char **)addresses);
+ break;
+ default:
+ break;
+ }
+ }
+ NuBlock *block = (NuBlock *) arg;
+ id args = [[NSClassFromString(@"NuCell") alloc] init];
+ [args setCar:address];
+ [block evalWithArguments:args context:nil];
+ [block release];
+ [args release];
+}
+
+- (void) resolveDomainName:(NSString *) name andDo:(NuBlock *) block
+{
+ [block retain];
+ evdns_resolve_ipv4([name cStringUsingEncoding:NSUTF8StringEncoding], 0, nunja_dns_gethostbyname_cb, block);
+}
+
+void nunja_http_request_done(struct evhttp_request *req, void *arg)
+{
+ NSData *data = nil;
+ if (req->response_code != HTTP_OK) {
+ if (req->response_code == HTTP_SEEOTHER) {
+ fprintf(stdout, "REDIRECTING\n");
+ //NSDictionary *headers = nunja_request_headers_helper(req);
+ return; // this is not handled yet.
+ }
+ fprintf(stdout, "FAILED to get OK (response = %d)\n", req->response_code);
+ }
+ else if (evhttp_find_header(req->input_headers, "Content-Type") == NULL) {
+ fprintf(stdout, "FAILED to find Content-Type\n");
+ }
+ else {
+ data = [NSData dataWithBytes:EVBUFFER_DATA(req->input_buffer) length:EVBUFFER_LENGTH(req->input_buffer)];
+ }
+ NuBlock *block = (NuBlock *) arg;
+ id args = [[NSClassFromString(@"NuCell") alloc] init];
+ [args setCar:data];
+ [block evalWithArguments:args context:nil];
+ [block release];
+ [args release];
+ fprintf(stdout, "end of callback\n");
+ // leaking...
+ //evhttp_connection_free(req->evcon);
+}
+
+- (void) getResourceFromHost:(NSString *) host address:(NSString *) address port:(int)port path:(NSString *)path andDo:(NuBlock *) block
+{
+ [block retain];
+ // make the connection
+ struct evhttp_connection *evcon = evhttp_connection_new([address cStringUsingEncoding:NSUTF8StringEncoding], port);
+ if (evcon == NULL) {
+ fprintf(stdout, "FAILED to connect\n");
+ id args = [[NSClassFromString(@"NuCell") alloc] init];
+ [block evalWithArguments:args context:nil];
+ [block release];
+ [args release];
+ return;
+ }
+ // make the request
+ struct evhttp_request *req = evhttp_request_new(nunja_http_request_done, block);
+ evhttp_add_header(req->output_headers, "Host", [host cStringUsingEncoding:NSUTF8StringEncoding]);
+ // give ownership of the request to the connection
+ if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, [path cStringUsingEncoding:NSUTF8StringEncoding]) == -1) {
+ fprintf(stdout, "FAILED to make the request \n");
+ }
+}
+
+- (void) postDataToHost:(NSString *) host address:(NSString *) address port:(int)port path:(NSString *)path data:(NSData *) data andDo:(NuBlock *) block
+{
+ [block retain];
+ // make the connection
+ struct evhttp_connection *evcon = evhttp_connection_new([address cStringUsingEncoding:NSUTF8StringEncoding], port);
+ if (evcon == NULL) {
+ fprintf(stdout, "FAILED to connect\n");
+ id args = [[NSClassFromString(@"NuCell") alloc] init];
+ [block evalWithArguments:args context:nil];
+ [block release];
+ [args release];
+ return;
+ }
+ // make the request
+ struct evhttp_request *req = evhttp_request_new(nunja_http_request_done, block);
+ evhttp_add_header(req->output_headers, "Host", [host cStringUsingEncoding:NSUTF8StringEncoding]);
+ evhttp_add_header(req->output_headers, "Content-Length", [[NSString stringWithFormat:@"%d", [data length]] cStringUsingEncoding:NSUTF8StringEncoding]);
+ evhttp_add_header(req->output_headers, "Content-Type", "application/x-www-form-urlencoded");
+ evbuffer_add(req->output_buffer, [data bytes], [data length]);
+
+ // give ownership of the request to the connection
+ if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, [path cStringUsingEncoding:NSUTF8StringEncoding]) == -1) {
+ fprintf(stdout, "FAILED to make the request \n");
+ }
+}
+
+@end
+
+@implementation Nunja
+
++ (Nunja *) nunja {
+ return [[[ConcreteNunja alloc] init] autorelease];
+}
+
++ (void) setVerbose:(BOOL) v
+{
+ verbose_nunja = v;
+}
+
++ (BOOL) verbose {return verbose_nunja;}
+
+- (void) run {
+}
+
+- (int) bindToAddress:(NSString *) address port:(int) port {
+ return 0;
+}
+
+- (void) setDelegate:(id) d
+{
+}
+
+- (id) delegate {
+ return nil;
+}
+
+static NSMutableDictionary *mimeTypes = nil;
+
++ (NSMutableDictionary *) mimeTypes {
+ return mimeTypes;
+}
+
++ (void) setMimeTypes:(NSMutableDictionary *) dictionary {
+ [dictionary retain];
+ [mimeTypes release];
+ mimeTypes = dictionary;
+}
+
++ (NSString *) mimeTypeForFileWithName:(NSString *) pathName {
+ if (mimeTypes) {
+ NSString *suffix = [[pathName componentsSeparatedByString:@"."] lastObject];
+ NSString *mimeType = [mimeTypes objectForKey:suffix];
+ if (mimeType)
+ return mimeType;
+ }
+ // default
+ return @"text/html; charset=utf-8";
+}
+
+@end
+
+int NunjaMain(int argc, const char *argv[], NSString *NunjaDelegateClassName)
+{
+ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+
+ int port = 5000;
+ NSString *site = @".";
+
+ BOOL localOnly = NO;
+ int i = 0;
+ while (i < argc) {
+ if (!strcmp(argv[i], "-s") ||
+ !strcmp(argv[i], "--site")) {
+ if (++i < argc) {
+ site = [[[NSString alloc] initWithCString:argv[i]] autorelease];
+ }
+ }
+ else if (!strcmp(argv[i], "-p") ||
+ !strcmp(argv[i], "--port")) {
+ if (++i < argc) {
+ port = atoi(argv[i]);
+ }
+ }
+ else if (!strcmp(argv[i], "-l") ||
+ !strcmp(argv[i], "--local")) {
+ localOnly = YES;
+ }
+ else if (!strcmp(argv[i], "-v") ||
+ !strcmp(argv[i], "--verbose")) {
+ [Nunja setVerbose:YES];
+ }
+ i++;
+ }
+
+ Nunja *nunja = [Nunja nunja];
+ if (localOnly) {
+ [nunja bindToAddress:@"127.0.0.1" port:port];
+ }
+ else {
+ [nunja bindToAddress:@"0.0.0.0" port:port];
+ }
+ Class NunjaDelegateClass = NunjaDelegateClassName ? NSClassFromString(NunjaDelegateClassName) : [NunjaDelegate class];
+ id delegate = [[[NunjaDelegateClass alloc] initWithSite:site] autorelease];
+ if ([delegate respondsToSelector:@selector(nunjaDidFinishLaunching)]) {
+ [delegate nunjaDidFinishLaunching];
+ }
+ if ([Nunja verbose]) {
+ [delegate dump];
+ }
+ [nunja setDelegate:delegate];
+ [nunja run];
+
+ [pool drain];
+ return 0;
+}
+
diff --git a/objc/NunjaDelegate.h b/objc/NunjaDelegate.h
new file mode 100644
index 0000000..24f5d7a
--- /dev/null
+++ b/objc/NunjaDelegate.h
@@ -0,0 +1,23 @@
+#import
+#import "Nunja.h"
+
+@class NunjaRequestHandler;
+@class NunjaRequestRouter;
+
+@interface NunjaDelegate : NSObject
+{
+ NunjaRequestHandler *defaultHandler;
+ NunjaRequestRouter *router;
+}
+
+- (id) initWithSite:(NSString *) site;
+
+- (void) addHandler:(NunjaRequestHandler *) handler;
+- (void) addHandlerWithHTTPMethod:(NSString *)httpMethod path:(NSString *)path block:(id)block;
+- (void) setDefaultHandlerWithBlock:(id) block;
+
+- (void) dump;
+
+@end
+
+
diff --git a/objc/NunjaDelegate.m b/objc/NunjaDelegate.m
new file mode 100644
index 0000000..43e0bcf
--- /dev/null
+++ b/objc/NunjaDelegate.m
@@ -0,0 +1,170 @@
+
+#import "NunjaDelegate.h"
+#import "NunjaRequestRouter.h"
+#import "NunjaRequestHandler.h"
+#import "Nunja.h"
+
+@implementation NunjaDelegate
+
+static NunjaDelegate *_sharedDelegate;
+
++ (NunjaDelegate *) sharedDelegate {
+ return _sharedDelegate;
+}
+
+- (id) init
+{
+ if (self = [super init]) {
+ self->router = [[NunjaRequestRouter routerWithToken:@""] retain];
+ }
+ _sharedDelegate = self;
+ return self;
+}
+
+- (id) initWithSite:(id) site
+{
+ self = [self init];
+
+ id parser = [NSClassFromString(@"Nu") parser];
+
+ // set working directory to site path
+ chdir([site cStringUsingEncoding:NSUTF8StringEncoding]);
+
+ // load site description
+ NSString *filename = [NSString stringWithFormat:@"site.nu", site];
+ NSString *sourcecode = [NSString stringWithContentsOfFile:filename];
+ if (sourcecode) {
+ [parser parseEval:sourcecode];
+ }
+ return self;
+}
+
+- (void) setDefaultHandlerWithBlock:(id) block {
+ id handler = [NunjaRequestHandler handlerWithHTTPMethod:@"GET" path:@"" block:block];
+ [handler retain];
+ [self->defaultHandler release];
+ self->defaultHandler = handler;
+}
+
+- (void) addHandler:(id) handler
+{
+ [self->router insertHandler:handler level:0];
+}
+
+- (void) addHandlerWithHTTPMethod:(NSString *)httpMethod path:(NSString *)path block:(id)block
+{
+ [self addHandler:[NunjaRequestHandler handlerWithHTTPMethod:httpMethod path:path block:block]];
+}
+
+- (void) dump
+{
+ [self->router dump:0];
+}
+
+- (void) handleRequest:(NunjaRequest *) request
+{
+ id path = [request path];
+ if ([Nunja verbose]) {
+ NSLog(@"REQUEST %@ %@ ----", [request HTTPMethod], path);
+ NSLog(@"%@", [request requestHeaders]);
+ }
+ [request setValue:@"Nunja" forResponseHeader:@"Server"];
+ [request setBindings:[NSMutableDictionary dictionary]];
+
+ id httpMethod = [request HTTPMethod];
+ if ([httpMethod isEqualToString:@"HEAD"])
+ httpMethod = @"GET";
+
+ id parts = [[NSString stringWithFormat:@"%@%@", httpMethod, [request path]] componentsSeparatedByString:@"/"];
+ if (([parts count] > 2) && [[parts lastObject] isEqualToString:@""]) {
+ parts = [parts subarrayWithRange:NSMakeRange(0, [parts count]-1)];
+ }
+
+ BOOL handled = NO; // [[NunjaCache sharedCache] handleRequest:request];
+
+ if (!handled) {
+ NunjaRequestHandler *handler = [router routeRequest:request parts:parts level:0];
+ if (handler) {
+ handled = [handler handleRequest:request];
+ }
+ }
+
+ if (!handled) { // does the path end in a '/'? If so, append index.html
+ unichar lastCharacter = [path characterAtIndex:[path length] - 1];
+ if (lastCharacter == '/') {
+ if (!handled) {
+ NSString *filename = [NSString stringWithFormat:@"public%@index.html", path];
+ if ([[NSFileManager defaultManager] fileExistsAtPath:filename]) {
+ NSData *data = [NSData dataWithContentsOfFile:filename];
+ [request setValue:[Nunja mimeTypeForFileWithName:filename] forResponseHeader:@"Content-Type"];
+ [request setValue:@"max-age=3600" forResponseHeader:@"Cache-Control"];
+ [request respondWithData:data];
+ handled = YES;
+ }
+ }
+ if (!handled) {
+ NSString *filename = [NSString stringWithFormat:@"public%@prog_index.m3u8", path];
+ if ([[NSFileManager defaultManager] fileExistsAtPath:filename]) {
+ NSData *data = [NSData dataWithContentsOfFile:filename];
+ [request setValue:[Nunja mimeTypeForFileWithName:filename] forResponseHeader:@"Content-Type"];
+ [request setValue:@"max-age=3600" forResponseHeader:@"Cache-Control"];
+ [request respondWithData:data];
+ handled = YES;
+ }
+ }
+ }
+ }
+
+ if (!handled) {
+ // look for a file or directory that matches the path
+ NSString *filename = [NSString stringWithFormat:@"public%@", path];
+ BOOL isDirectory = NO;
+ BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:filename isDirectory:&isDirectory];
+ if (fileExists) {
+ if (isDirectory) {
+ unichar lastCharacter = [path characterAtIndex:[path length] - 1];
+ if (lastCharacter != '/') {
+ // for a directory, redirect to the same path with '/' appended
+ [request setValue:[path stringByAppendingString:@"/"] forResponseHeader:@"Location"];
+ [request respondWithCode:301 message:@"moved permanently" string:@"Moved Permanently"];
+ handled = YES;
+ }
+ } else {
+ // for a file, send its contents
+ NSData *data = [NSData dataWithContentsOfFile:filename];
+ [request setValue:[Nunja mimeTypeForFileWithName:filename] forResponseHeader:@"Content-Type"];
+ [request setValue:@"max-age=3600" forResponseHeader:@"Cache-Control"];
+ [request respondWithData:data];
+ handled = YES;
+ }
+ }
+ }
+
+ if (!handled) { // try appending .html to the path
+ NSString *filename = [NSString stringWithFormat:@"public%@.html", path];
+ if ([[NSFileManager defaultManager] fileExistsAtPath:filename]) {
+ NSData *data = [NSData dataWithContentsOfFile:filename];
+ [request setValue:@"text/html" forResponseHeader:@"Content-Type"];
+ [request setValue:@"max-age=3600" forResponseHeader:@"Cache-Control"];
+ [request respondWithData:data];
+ handled = YES;
+ }
+ }
+
+ if (!handled) {
+ if (defaultHandler) {
+ [defaultHandler handleRequest:request];
+ }
+ else {
+ [request respondWithCode:404
+ message:@"Not Found"
+ string:[NSString stringWithFormat:@"Not Found. You said: %@ %@", [request HTTPMethod], [request path]]];
+ }
+ }
+}
+
+- (void) nunjaDidFinishLaunching {
+
+}
+
+@end
diff --git a/objc/NunjaRequest.h b/objc/NunjaRequest.h
new file mode 100644
index 0000000..37cf684
--- /dev/null
+++ b/objc/NunjaRequest.h
@@ -0,0 +1,60 @@
+
+#import
+
+#include
+#include
+#include
+#include
+#include
+
+void nunja_response_helper(struct evhttp_request *req, int code, NSString *message, NSData *data);
+NSDictionary *nunja_request_headers_helper(struct evhttp_request *req);
+
+@class Nunja;
+
+@interface NunjaRequest : NSObject
+{
+ Nunja *nunja;
+ struct evhttp_request *req;
+ NSString *_uri;
+ NSString *_path;
+ NSDictionary *_parameters;
+ NSDictionary *_query;
+ NSDictionary *_bindings;
+ id _cookies;
+ int _responded;
+ int _responseCode;
+ NSString *_responseMessage;
+}
+
+- (id) initWithNunja:(Nunja *)n request:(struct evhttp_request *)r;
+- (Nunja *) nunja;
+- (NSString *) uri;
+- (NSString *) path;
+- (NSDictionary *) parameters;
+- (NSDictionary *) query;
+- (id) bindings;
+- (void) setBindings:(id) bindings;
+- (NSData *) body;
+- (NSString *) HTTPMethod;
+- (NSString *) remoteHost;
+- (int) remotePort;
+- (NSDictionary *) requestHeaders;
+- (NSDictionary *) responseHeaders;
+- (int) setValue:(NSString *) value forResponseHeader:(NSString *) key;
+- (NSString *) valueForResponseHeader:(NSString *) key;
+- (int) removeResponseHeader:(NSString *) key;
+- (void) clearResponseHeaders;
+- (int) responseCode;
+- (void) setResponseCode:(int) code message:(NSString *) message;
+- (int) setValue:(NSString *) value forResponseHeader:(NSString *) key;
+- (BOOL) respondWithString:(NSString *) string;
+- (BOOL) respondWithData:(NSData *) data;
+- (BOOL) respondWithCode:(int) code message:(NSString *) message string:(NSString *) string;
+- (BOOL) respondWithCode:(int) code message:(NSString *) message data:(NSData *) data;
+- (NSDictionary *) cookies;
+- (void) setContentType:(NSString *)content_type;
+- (int) redirectResponseToLocation:(NSString *) location;
+
+
+@end
diff --git a/objc/NunjaRequest.m b/objc/NunjaRequest.m
new file mode 100644
index 0000000..c8fe662
--- /dev/null
+++ b/objc/NunjaRequest.m
@@ -0,0 +1,289 @@
+#import "NunjaRequest.h"
+#import "Nunja.h"
+#import
+
+#include
+#include
+
+@interface NSString (Helpers)
+- (NSDictionary *) urlQueryDictionary;
+@end
+
+@implementation NunjaRequest
+
+- (id) initWithNunja:(Nunja *)n request:(struct evhttp_request *)r
+{
+ [super init];
+ nunja = n;
+ req = r;
+ // get the URI
+ _uri = [[NSString alloc] initWithCString:evhttp_request_uri(req) encoding:NSUTF8StringEncoding];
+ // scan for the path
+ int max = [_uri length];
+ int base = 0;
+ int i = 0;
+ unichar c = 0;
+ while ((i < max) && ((c = [_uri characterAtIndex:i])) && (c != ';') && (c != '?'))
+ i++;
+ _path = [[_uri substringToIndex:i] retain];
+ // if necessary, scan the object parameters
+ _parameters = nil;
+ if (c == ';') {
+ i = i + 1;
+ base = i;
+ while ((i < max) && ((c = [_uri characterAtIndex:i])) && (c != '?'))
+ i++;
+ NSString *parameterString = [_uri substringWithRange:NSMakeRange(base, i-base)];
+ _parameters = [[parameterString urlQueryDictionary] retain];
+ }
+ // if necessary, scan the query string
+ _query = nil;
+ if (c == '?') {
+ i = i + 1;
+ base = i;
+ while ((i < max) && ((c = [_uri characterAtIndex:i])))
+ i++;
+ NSString *queryString = [_uri substringWithRange:NSMakeRange(base, i-base)];
+ _query = [[queryString urlQueryDictionary] retain];
+ }
+ // we haven't responded yet
+ _responded = NO;
+ // default response code is that everything is ok
+ _responseCode = HTTP_OK;
+ _responseMessage = @"OK";
+ return self;
+}
+
+- (void) dealloc
+{
+ [_uri release];
+ [_path release];
+ [_parameters release];
+ [_query release];
+ [_bindings release];
+ [_cookies release];
+ [super dealloc];
+}
+
+- (Nunja *) nunja {return nunja;}
+
+- (NSString *) uri
+{
+ return _uri;
+}
+
+- (NSString *) path
+{
+ return _path;
+}
+
+- (NSDictionary *) parameters
+{
+ return _parameters ? _parameters : [NSDictionary dictionary];
+}
+
+- (NSDictionary *) query
+{
+ return _query ? _query : [NSDictionary dictionary];
+}
+
+- (id) bindings
+{
+ return _bindings ? _bindings : [NSDictionary dictionary];
+}
+
+- (void) setBindings:(id) bindings
+{
+ [bindings retain];
+ [_bindings release];
+ _bindings = bindings;
+}
+
+- (NSData *) body
+{
+ if (!req->input_buffer->buffer)
+ return nil;
+ else {
+ NSData *data = [NSData dataWithBytes:req->input_buffer->buffer length:req->input_buffer->off];
+ return data;
+ }
+}
+
+- (NSString *) HTTPMethod
+{
+ switch (req->type) {
+ case EVHTTP_REQ_GET:
+ return @"GET";
+ case EVHTTP_REQ_POST:
+ return @"POST";
+ case EVHTTP_REQ_HEAD:
+ return @"HEAD";
+ default:
+ return @"UNKNOWN";
+ }
+}
+
+- (NSString *) remoteHost
+{
+ return [NSString stringWithCString:req->remote_host encoding:NSUTF8StringEncoding];
+}
+
+- (int) remotePort
+{
+ return req->remote_port;
+}
+
+NSDictionary *nunja_request_headers_helper(struct evhttp_request *req)
+{
+ NSMutableDictionary *dict = [NSMutableDictionary dictionary];
+ struct evkeyval *header;
+ TAILQ_FOREACH(header, req->input_headers, next) {
+ [dict setObject:[NSString stringWithCString:header->value encoding:NSUTF8StringEncoding]
+ forKey:[NSString stringWithCString:header->key encoding:NSUTF8StringEncoding]];
+ }
+ return dict;
+}
+
+- (NSDictionary *) requestHeaders
+{
+ return nunja_request_headers_helper(req);
+}
+
+static NSDictionary *nunja_response_headers_helper(struct evhttp_request *req)
+{
+ NSMutableDictionary *dict = [NSMutableDictionary dictionary];
+ struct evkeyval *header;
+ TAILQ_FOREACH(header, req->output_headers, next) {
+ NSString *value = [NSString stringWithCString:header->value encoding:NSUTF8StringEncoding];
+ NSString *key = [NSString stringWithCString:header->key encoding:NSUTF8StringEncoding];
+ if (value && key) {
+ [dict setObject:value forKey:key];
+ }
+ }
+ return dict;
+}
+
+- (NSDictionary *) responseHeaders
+{
+ return nunja_response_headers_helper(req);
+}
+
+- (int) setValue:(NSString *) value forResponseHeader:(NSString *) key
+{
+ return evhttp_add_header(req->output_headers, [key cStringUsingEncoding:NSUTF8StringEncoding], [value cStringUsingEncoding:NSUTF8StringEncoding]);
+}
+
+- (NSString *) valueForResponseHeader:(NSString *) key
+{
+ const char *value = evhttp_find_header(req->output_headers, [key cStringUsingEncoding:NSUTF8StringEncoding]);
+ return value ? [NSString stringWithCString:value encoding:NSUTF8StringEncoding] : nil;
+}
+
+- (int) removeResponseHeader:(NSString *) key
+{
+ return evhttp_remove_header(req->output_headers, [key cStringUsingEncoding:NSUTF8StringEncoding]);
+}
+
+- (void) clearResponseHeaders
+{
+ evhttp_clear_headers(req->output_headers);
+}
+
+- (int) responseCode
+{
+ return _responseCode;
+}
+
+- (void) setResponseCode:(int) code message:(NSString *) message
+{
+ _responseCode = code;
+ [message retain];
+ [_responseMessage release];
+ _responseMessage = message;
+}
+
+void nunja_response_helper(struct evhttp_request *req, int code, NSString *message, NSData *data)
+{
+ if ([Nunja verbose]) {
+ NSLog(@"RESPONSE %d %@ %@", code, message, [nunja_response_headers_helper(req) description]);
+ }
+ struct evbuffer *buf = evbuffer_new();
+ if (buf == NULL) {
+ NSLog(@"FATAL: failed to create response buffer");
+ assert(0);
+ }
+ evbuffer_add(buf, [data bytes], [data length]);
+ evhttp_send_reply(req, code, [message cStringUsingEncoding:NSUTF8StringEncoding], buf);
+ evbuffer_free(buf);
+}
+
+- (BOOL) respondWithString:(NSString *) string
+{
+ if (!_responded) {
+ nunja_response_helper(req, _responseCode, _responseMessage, [string dataUsingEncoding:NSUTF8StringEncoding]);
+ _responded = YES;
+ }
+ return YES;
+}
+
+- (BOOL) respondWithData:(NSData *) data
+{
+ if (!_responded) {
+ nunja_response_helper(req, _responseCode, _responseMessage, data);
+ _responded = YES;
+ }
+ return YES;
+}
+
+- (BOOL) respondWithCode:(int) code message:(NSString *) message string:(NSString *) string
+{
+ if (!_responded) {
+ nunja_response_helper(req, code, message, [string dataUsingEncoding:NSUTF8StringEncoding]);
+ _responded = YES;
+ }
+ return YES;
+}
+
+- (BOOL) respondWithCode:(int) code message:(NSString *) message data:(NSData *) data
+{
+ if (!_responded) {
+ nunja_response_helper(req, code, message, data);
+ _responded = YES;
+ }
+ return YES;
+}
+
+- (NSDictionary *) cookies
+{
+ static id cookie_pattern = nil;
+ if (!cookie_pattern) {
+ cookie_pattern = [[NSClassFromString(@"NuRegex") regexWithPattern:@"[ ]*([^=]*)=(.*)"] retain];
+ }
+ if (!_cookies) {
+ _cookies = [[NSMutableDictionary alloc] init];
+ NSString *cookieText = [[self requestHeaders] objectForKey:@"Cookie"];
+ NSArray *parts = [cookieText componentsSeparatedByString:@";"];
+ for (int i = 0; i < [parts count]; i++) {
+ NSString *cookieDescription = [parts objectAtIndex:i];
+ id match = [cookie_pattern findInString:cookieDescription];
+ if (match) {
+ [_cookies setObject:[match groupAtIndex:2] forKey:[match groupAtIndex:1]];
+ }
+ }
+ }
+ return _cookies;
+}
+
+- (void) setContentType:(NSString *)content_type
+{
+ [self setValue:content_type forResponseHeader:@"Content-Type"];
+}
+
+- (int) redirectResponseToLocation:(NSString *) location
+{
+ [self setValue:location forResponseHeader:@"Location"];
+ [self respondWithCode:303 message:@"redirecting" string:@"redirecting"];
+ return YES;
+}
+
+@end
diff --git a/objc/NunjaRequestHandler.h b/objc/NunjaRequestHandler.h
new file mode 100644
index 0000000..fd95ec7
--- /dev/null
+++ b/objc/NunjaRequestHandler.h
@@ -0,0 +1,20 @@
+
+#import "Nunja.h"
+#import "NunjaRequest.h"
+
+@interface NunjaRequestHandler : NSObject {
+ NSString *httpMethod;
+ NSString *path;
+ id block; // A Nu or C block to be invoked to handle the request.
+
+ NSMutableArray *parts; // internal, used to expand pattern for request routing
+}
+
++ (NunjaRequestHandler *) handlerWithHTTPMethod:(id)httpMethod path:(id)path block:(id)block;
+
+- (NSString *) httpMethod;
+- (NSString *) path;
+- (NSMutableArray *) parts;
+
+- (BOOL) handleRequest:(NunjaRequest *)request;
+@end
\ No newline at end of file
diff --git a/objc/NunjaRequestHandler.m b/objc/NunjaRequestHandler.m
new file mode 100644
index 0000000..0c44681
--- /dev/null
+++ b/objc/NunjaRequestHandler.m
@@ -0,0 +1,91 @@
+#import "NunjaRequestHandler.h"
+#import "NunjaRequest.h"
+#import
+
+@implementation NunjaRequestHandler
+
+- (NSString *) path {
+ return path;
+}
+
+- (NSString *) httpMethod {
+ return httpMethod;
+}
+
+- (NSMutableArray *) parts {
+ return self->parts;
+}
+
++ (NunjaRequestHandler *) handlerWithHTTPMethod:(id)httpMethod path:(id)path block:(id)block
+{
+ NunjaRequestHandler *handler = [[[NunjaRequestHandler alloc] init] autorelease];
+ handler->httpMethod = [httpMethod retain];
+ handler->path = [path retain];
+ handler->parts = [[[NSString stringWithFormat:@"%@%@", httpMethod, path] componentsSeparatedByString:@"/"] retain];
+ handler->block = [block retain];
+ return handler;
+}
+
+// Handle a request. Used internally.
+- (BOOL) handleRequest:(NunjaRequest *)request
+{
+ if ([Nunja verbose]) {
+ NSLog(@"handling request %@", [request uri]);
+ NSLog(@"request from host %@ port %d", [request remoteHost], [request remotePort]);
+ }
+
+ id args = [[[NSClassFromString(@"NuCell") alloc] init] autorelease];
+ [args setCar:request];
+
+ id body = nil;
+ if ([block isKindOfClass:NSClassFromString(@"NuBlock")]) {
+ // evaluate block with request as the single argument
+ body = [block evalWithArguments:args context:[NSMutableDictionary dictionary]];
+ }
+#ifdef DARWIN
+ else {
+ // evaluate block as a C block
+ body = ((id(^)(id)) block)(request);
+ }
+#endif
+
+ if ([Nunja verbose]) {
+ NSLog(@"evaluated with status %d", [request responseCode]);
+ }
+
+ static id text_html_pattern = nil;
+ if (!text_html_pattern) {
+ text_html_pattern = [[NSClassFromString(@"NuRegex") regexWithPattern:@"^text/html.*$"] retain];
+ }
+
+ id content_type;
+
+ if (!body || (body == [NSNull null])) {
+ // return without responding, this means the handler has rejected the URL
+ return NO;
+ }
+ else if ([body isKindOfClass:[NSData class]]) {
+ // return data objects as-is
+ // we should set the content type if it isn't set
+ [request respondWithData:body];
+ return YES; // just non-nil
+ }
+ else if (![body isKindOfClass:[NSString class]]) {
+ // return other non-strings as their stringValues
+ [request respondWithString:[body stringValue]];
+ return YES;
+ }
+ else if ((content_type = [request valueForResponseHeader:@"Content-Type"]) && ![text_html_pattern findInString:content_type]) {
+ // if a content type is set and it isn't text/html, return string as-is
+ [request respondWithString:body];
+ return YES;
+ }
+ else {
+ // return string as html
+ [request setContentType:@"text/html; charset=UTF-8"];
+ [request respondWithString:body];
+ return YES;
+ }
+}
+
+@end
diff --git a/objc/NunjaRequestRouter.h b/objc/NunjaRequestRouter.h
new file mode 100644
index 0000000..768edff
--- /dev/null
+++ b/objc/NunjaRequestRouter.h
@@ -0,0 +1,18 @@
+#import
+
+@class NunjaRequest;
+@class NunjaRequestHandler;
+
+@interface NunjaRequestRouter : NSObject {
+ NSMutableDictionary *contents;
+ NSString *token;
+ NunjaRequestHandler *handler;
+}
+
++ (NunjaRequestRouter *) routerWithToken:(id) token;
+- (NSString *) token;
+- (void) dump:(int) level;
+- (id) routeRequest:(NunjaRequest *) request parts:(NSArray *) parts level:(int) level;
+- (void) insertHandler:(NunjaRequestHandler *) handler level:(int) level;
+@end
+
diff --git a/objc/NunjaRequestRouter.m b/objc/NunjaRequestRouter.m
new file mode 100644
index 0000000..dbb375a
--- /dev/null
+++ b/objc/NunjaRequestRouter.m
@@ -0,0 +1,85 @@
+
+#import "NunjaRequestHandler.h"
+#import "NunjaRequestRouter.h"
+#import "NunjaRequest.h"
+
+NSString *spaces(int n) {
+ NSMutableString *result = [NSMutableString string];
+ for (int i = 0; i < n; i++) {
+ [result appendString:@" "];
+ }
+ return result;
+}
+
+
+@implementation NunjaRequestRouter
+
++ (NunjaRequestRouter *) routerWithToken:(NSString *) token
+{
+ NunjaRequestRouter *router = [[[self alloc] init] autorelease];
+ router->contents = [[NSMutableDictionary alloc] init];
+ router->token = [token retain];
+ return router;
+}
+
+- (id) token {
+ return token;
+}
+
+- (void) dump:(int) level
+{
+ NSLog(@"%@%@\t%@", spaces(level), self->token, self->handler);
+ id keys = [self->contents allKeys];
+ for (int i = 0; i < [keys count]; i++) {
+ id key = [keys objectAtIndex:i];
+ id value = [self->contents objectForKey:key];
+ [value dump:(level+1)];
+ }
+}
+
+- (id) routeRequest:(NunjaRequest *) request parts:(NSArray *) parts level:(int) level
+{
+ if (level == [parts count]) {
+ return self->handler;
+ }
+ else {
+ id key = [parts objectAtIndex:level];
+ id response;
+ id child = [self->contents objectForKey:key];
+ if (child && (response = [child routeRequest:request parts:parts level:(level+1)])) {
+ // if the response is non-null, we have a match
+ return response;
+ }
+ else if (child = [self->contents objectForKey:@":"]) {
+ [[request bindings] setObject:key forKey:[[child token] substringToIndex:([[child token] length]-1)]];
+ return [child routeRequest:request parts:parts level:(level + 1)];
+ }
+ else {
+ return nil;
+ }
+ }
+}
+
+- (void) insertHandler:(NunjaRequestHandler *) h level:(int) level
+{
+ if (level == [[h parts] count]) {
+ self->handler = [h retain];
+ }
+ else {
+ id key = [[h parts] objectAtIndex:level];
+ BOOL key_is_wildcard = ([key length] > 0) && ([key characterAtIndex:([key length] - 1)] == ':');
+ id child = [self->contents objectForKey:(key_is_wildcard ? @":" : key)];
+ if (!child) {
+ child = [NunjaRequestRouter routerWithToken:key];
+ }
+ if (([key length] > 0) && (([key characterAtIndex:0] == ':') || ([key characterAtIndex:([key length] -1)] == ':'))) {
+ [self->contents setObject:child forKey:@":"];
+ }
+ else {
+ [self->contents setObject:child forKey:key];
+ }
+ [child insertHandler:h level:level+1];
+ }
+}
+
+@end
diff --git a/objc/util.m b/objc/Nunja_daemonize.m
similarity index 85%
rename from objc/util.m
rename to objc/Nunja_daemonize.m
index 2d8df5d..2b29e96 100644
--- a/objc/util.m
+++ b/objc/Nunja_daemonize.m
@@ -1,5 +1,5 @@
/*!
-@file util.m
+@file Nunja_daemonize.m
@discussion General utilities for the Nunja web server.
@copyright Copyright (c) 2008 Neon Design Technology, Inc.
@@ -27,9 +27,13 @@
#include
#import
+#import "Nunja.h"
// http://www.netzmafia.de/skripten/unix/linux-daemon-howto.html
-void daemonize()
+
+@implementation Nunja (daemonize)
+
++ (void) daemonize
{
pid_t pid, sid;
@@ -68,16 +72,4 @@ void daemonize()
close(STDERR_FILENO);
}
-@implementation NSFileManager (Nunja)
-
-- (BOOL) directoryExistsAtPath:(NSString *) path
-{
- BOOL isDirectory = NO;
- BOOL fileExists = [self fileExistsAtPath:path isDirectory:&isDirectory];
- if (!fileExists)
- return NO;
- else
- return isDirectory;
-}
-
@end
diff --git a/objc/markup.m b/objc/markup.m
deleted file mode 100644
index e1e0d14..0000000
--- a/objc/markup.m
+++ /dev/null
@@ -1,96 +0,0 @@
-#import
-
-@interface NuOperator : NSObject
-{
-}
-
-- (id) evalWithArguments:(id) cdr context:(NSMutableDictionary *) context;
-- (id) callWithArguments:(id) cdr context:(NSMutableDictionary *) context;
-@end
-
-@interface NunjaMarkupOperator : NuOperator
-{
- NSString *tag;
- NSString *prefix;
-}
-
-- (id) initWithTag:(NSString *) tag;
-- (id) initWithTag:(NSString *) tag prefix:(NSString *) prefix;
-@end
-
-@implementation NunjaMarkupOperator
-
-+ (id) operatorWithTag:(NSString *) _tag
-{
- return [[[self alloc] initWithTag:_tag] autorelease];
-}
-
-+ (id) operatorWithTag:(NSString *) _tag prefix:(NSString *) _prefix
-{
- return [[[self alloc] initWithTag:_tag prefix:_prefix] autorelease];
-}
-
-- (id) initWithTag:(NSString *) _tag
-{
- return [self initWithTag:_tag prefix:nil];
-}
-
-- (id) initWithTag:(NSString *) _tag prefix:(NSString *) _prefix
-{
- self = [super init];
- tag = _tag ? [_tag retain] : @"undefined-element";
- prefix = _prefix ? [_prefix retain] : @"";
- return self;
-}
-
-- (id) callWithArguments:(id)cdr context:(NSMutableDictionary *)context
-{
- NSMutableString *body = [NSMutableString string];
- NSMutableString *attributes = [NSMutableString string];
-
- static id NuSymbol = nil;
- if (!NuSymbol) {
- NuSymbol = NSClassFromString(@"NuSymbol");
- }
-
- id cursor = cdr;
- while (cursor && (cursor != [NSNull null])) {
- id item = [cursor car];
- if ([item isKindOfClass:[NuSymbol class]] && [item isLabel]) {
- cursor = [cursor cdr];
- if (cursor && (cursor != [NSNull null])) {
- id value = [[cursor car] evalWithContext:context];
- [attributes appendFormat:@" %@=\"%@\"", [item labelName], [value stringValue]];
- }
- }
- else {
- id evaluatedItem = [item evalWithContext:context];
- if ([evaluatedItem isKindOfClass:[NSString class]]) {
- [body appendString:evaluatedItem];
- }
- else if ([evaluatedItem isKindOfClass:[NSArray class]]) {
- int max = [evaluatedItem count];
- for (int i = 0; i < max; i++) {
- [body appendString:[evaluatedItem objectAtIndex:i]];
- }
- }
- else if (evaluatedItem == [NSNull null]) {
- // do nothing
- }
- else {
- [body appendString:[evaluatedItem stringValue]];
- }
- }
- if (cursor && (cursor != [NSNull null]))
- cursor = [cursor cdr];
- }
-
- if ([body length]) {
- return [NSString stringWithFormat:@"%@<%@%@>%@%@>", prefix, tag, attributes, body, tag];
- }
- else {
- return [NSString stringWithFormat:@"%@<%@%@/>", prefix, tag, attributes];
- }
-}
-
-@end
diff --git a/objc/nunja.h b/objc/nunja.h
deleted file mode 100644
index 941f453..0000000
--- a/objc/nunja.h
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-#import
-
-@interface SuperNunja : NSObject {}
-@end
\ No newline at end of file
diff --git a/objc/nunja.m b/objc/nunja.m
deleted file mode 100644
index 9dc8c09..0000000
--- a/objc/nunja.m
+++ /dev/null
@@ -1,548 +0,0 @@
-/*!
-@file nunja.m
-@discussion Objective-C components of the Nunja web server.
-@copyright Copyright (c) 2008 Neon Design Technology, Inc.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-#include
-#include
-#include
-#include
-#include
-#define HTTP_SEEOTHER 303
-#define HTTP_DENIED 403
-
-#include
-#include
-
-#import
-#import
-#import "nunja.h"
-
-void NunjaInit()
-{
- static initialized = 0;
- if (!initialized) {
- initialized = 1;
- [Nu loadNuFile:@"nunja" fromBundleWithIdentifier:@"nu.programming.nunja" withContext:nil];
- }
-}
-
-@class Nunja;
-
-@implementation SuperNunja
-@end
-
-static BOOL verbose_nunja = NO;
-static BOOL local_nunja = NO;
-static BOOL autotags = YES;
-
-@interface NunjaRequest : NSObject
-{
- Nunja *nunja;
- struct evhttp_request *req;
- NSString *_uri;
- NSString *_path;
- NSDictionary *_parameters;
- NSDictionary *_query;
- id _match;
- NSDictionary *_bindings;
- id _cookies;
- int _responded;
- int _responseCode;
- NSString *_responseMessage;
-}
-
-@end
-
-@implementation NunjaRequest
-
-- (id) initWithNunja:(Nunja *)n request:(struct evhttp_request *)r
-{
- [super init];
- nunja = n;
- req = r;
- // get the URI
- _uri = [[NSString alloc] initWithCString:evhttp_request_uri(req) encoding:NSUTF8StringEncoding];
- // scan for the path
- int max = [_uri length];
- int base = 0;
- int i = 0;
- unichar c;
- while ((i < max) && ((c = [_uri characterAtIndex:i])) && (c != ';') && (c != '?'))
- i++;
- _path = [[_uri substringToIndex:i] retain];
- // if necessary, scan the object parameters
- _parameters = nil;
- if (c == ';') {
- i = i + 1;
- base = i;
- while ((i < max) && ((c = [_uri characterAtIndex:i])) && (c != '?'))
- i++;
- NSString *parameterString = [_uri substringWithRange:NSMakeRange(base, i-base)];
- _parameters = [[parameterString urlQueryDictionary] retain];
- }
- // if necessary, scan the query string
- _query = nil;
- if (c == '?') {
- i = i + 1;
- base = i;
- while ((i < max) && ((c = [_uri characterAtIndex:i])))
- i++;
- NSString *queryString = [_uri substringWithRange:NSMakeRange(base, i-base)];
- _query = [[queryString urlQueryDictionary] retain];
- }
- // we haven't responded yet
- _responded = NO;
- // default response code is that everything is ok
- _responseCode = HTTP_OK;
- _responseMessage = @"OK";
- return self;
-}
-
-- (void) dealloc
-{
- [_uri release];
- [_path release];
- [_parameters release];
- [_query release];
- [_match release];
- [_bindings release];
- [_cookies release];
- [super dealloc];
-}
-
-- (Nunja *) nunja {return nunja;}
-
-- (NSString *) uri
-{
- return _uri;
-}
-
-- (NSString *) path
-{
- return _path;
-}
-
-- (NSDictionary *) parameters
-{
- return _parameters;
-}
-
-- (NSDictionary *) query
-{
- return _query;
-}
-
-- (id) match
-{
- return _match;
-}
-
-- (void) setMatch:(id) match
-{
- [match retain];
- [_match release];
- _match = match;
-}
-
-- (id) bindings
-{
- return _bindings;
-}
-
-- (void) setBindings:(id) bindings
-{
- [bindings retain];
- [_bindings release];
- _bindings = bindings;
-}
-
-- (NSData *) body
-{
- if (!req->input_buffer->buffer)
- return nil;
- else {
- NSData *data = [NSData dataWithBytes:req->input_buffer->buffer length:req->input_buffer->off];
- }
-}
-
-- (NSString *) command
-{
- switch (req->type) {
- case EVHTTP_REQ_GET:
- return @"GET";
- case EVHTTP_REQ_POST:
- return @"POST";
- case EVHTTP_REQ_HEAD:
- return @"HEAD";
- default:
- return @"UNKNOWN";
- }
-}
-
-- (NSString *) method
-{
- return [self command];
-}
-
-- (NSString *) remoteHost
-{
- return [NSString stringWithCString:req->remote_host encoding:NSUTF8StringEncoding];
-}
-
-- (int) remotePort
-{
- return req->remote_port;
-}
-
-static NSDictionary *nunja_request_headers_helper(struct evhttp_request *req)
-{
- NSMutableDictionary *dict = [NSMutableDictionary dictionary];
- struct evkeyval *header;
- TAILQ_FOREACH(header, req->input_headers, next) {
- [dict setObject:[NSString stringWithCString:header->value encoding:NSUTF8StringEncoding]
- forKey:[NSString stringWithCString:header->key encoding:NSUTF8StringEncoding]];
- }
- return dict;
-}
-
-- (NSDictionary *) requestHeaders
-{
- return nunja_request_headers_helper(req);
-}
-
-static NSDictionary *nunja_response_headers_helper(struct evhttp_request *req)
-{
- NSMutableDictionary *dict = [NSMutableDictionary dictionary];
- struct evkeyval *header;
- TAILQ_FOREACH(header, req->output_headers, next) {
- [dict setObject:[NSString stringWithCString:header->value encoding:NSUTF8StringEncoding]
- forKey:[NSString stringWithCString:header->key encoding:NSUTF8StringEncoding]];
- }
- return dict;
-}
-
-- (NSDictionary *) responseHeaders
-{
- return nunja_response_headers_helper(req);
-}
-
-- (int) setValue:(const char *) value forResponseHeader:(const char *) key
-{
- return evhttp_add_header(req->output_headers, key, value);
-}
-
-- (NSString *) valueForResponseHeader:(const char *) key
-{
- const char *value = evhttp_find_header(req->output_headers, key);
- return value ? [NSString stringWithCString:value encoding:NSUTF8StringEncoding] : nil;
-}
-
-- (int) removeResponseHeader:(const char *) key
-{
- return evhttp_remove_header(req->output_headers, key);
-}
-
-- (void) clearResponseHeaders
-{
- evhttp_clear_headers(req->output_headers);
-}
-
-- (int) responseCode {
- return _responseCode;
-}
-
-- (void) setResponseCode:(int) code message:(NSString *) message {
- _responseCode = code;
- [message retain];
- [_responseMessage release];
- _responseMessage = message;
-}
-
-static void nunja_response_helper(struct evhttp_request *req, int code, NSString *message, NSData *data)
-{
- if (verbose_nunja) {
- NSLog(@"RESPONSE %d %@ %@", code, message, [nunja_response_headers_helper(req) description]);
- }
- struct evbuffer *buf = evbuffer_new();
- if (buf == NULL) err(1, "failed to create response buffer");
- evbuffer_add(buf, [data bytes], [data length]);
- evhttp_send_reply(req, code, [message cStringUsingEncoding:NSUTF8StringEncoding], buf);
- evbuffer_free(buf);
-}
-
-- (BOOL) respondWithString:(NSString *) string
-{
- if (!_responded) {
- nunja_response_helper(req, _responseCode, _responseMessage, [string dataUsingEncoding:NSUTF8StringEncoding]);
- _responded = YES;
- }
- return YES;
-}
-
-- (BOOL) respondWithData:(NSData *) data
-{
- if (!_responded) {
- nunja_response_helper(req, _responseCode, _responseMessage, data);
- _responded = YES;
- }
- return YES;
-}
-
-- (BOOL) respondWithCode:(int) code message:(NSString *) message string:(NSString *) string
-{
- if (!_responded) {
- nunja_response_helper(req, code, message, [string dataUsingEncoding:NSUTF8StringEncoding]);
- _responded = YES;
- }
- return YES;
-}
-
-- (BOOL) respondWithCode:(int) code message:(NSString *) message data:(NSData *) data
-{
- if (!_responded) {
- nunja_response_helper(req, code, message, data);
- _responded = YES;
- }
- return YES;
-}
-
-@end
-
-@protocol NunjaController
-- (void) handleRequest:(NunjaRequest *)request;
-@end
-
-@interface Nunja : SuperNunja
-{
- struct event_base *event_base;
- struct evhttp *httpd;
- id controller;
-}
-
-- (id) controller;
-@end
-
-@implementation Nunja
-
-+ (void) setVerbose:(BOOL) v
-{
- verbose_nunja = v;
-}
-
-+ (BOOL) verbose {return verbose_nunja;}
-
-+ (void) setLocalOnly:(BOOL) l
-{
- local_nunja = l;
-}
-
-+ (BOOL) localOnly {return local_nunja;}
-
-+ (void) load
-{
- NunjaInit();
-}
-
-static void nunja_request_handler(struct evhttp_request *req, void *nunja_pointer)
-{
- Nunja *nunja = (Nunja *) nunja_pointer;
- id controller = [nunja controller];
- if (controller) {
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- NunjaRequest *request = [[NunjaRequest alloc] initWithNunja:nunja request:req];
- [controller handleRequest:request];
- [request release];
- [pool release];
- }
- else {
- nunja_response_helper(req, HTTP_OK, @"OK",
- [[NSString stringWithFormat:@"Please set the Nunja server controller.
If you are running nunjad, use the '-s' option to specify a site.
\nRequest: %s\n",
- evhttp_request_uri(req)]
- dataUsingEncoding:NSUTF8StringEncoding]);
- }
-}
-
-- (id) init
-{
- [super init];
- event_base = event_init();
- evdns_init();
- httpd = evhttp_new(event_base);
- evhttp_set_gencb(httpd, nunja_request_handler, self);
- controller = nil;
- return self;
-}
-
-- (int) bindToAddress:(const char *) address port:(int) port
-{
- return evhttp_bind_socket(httpd, address, port);
-}
-
-- (void) run
-{
- event_base_dispatch(event_base);
-}
-
-- (void) dealloc
-{
- evhttp_free(httpd);
- [super dealloc];
-}
-
-- (id) controller
-{
- return controller;
-}
-
-- (void) setController:(id) d
-{
- [d retain];
- [controller release];
- controller = d;
-}
-
-@class NuBlock;
-@class NuCell;
-
-static void nunja_dns_gethostbyname_cb(int result, char type, int count, int ttl, void *addresses, void *arg)
-{
- id address = nil;
- if (result == DNS_ERR_TIMEOUT) {
- fprintf(stdout, "[Timed out] ");
- }
- else if (result != DNS_ERR_NONE) {
- fprintf(stdout, "[Error code %d] ", result);
- }
- else {
- fprintf(stdout, "type: %d, count: %d, ttl: %d\n", type, count, ttl);
- switch (type) {
- case DNS_IPv4_A:
- {
- struct in_addr *in_addrs = addresses;
- if (ttl < 0) {
- // invalid resolution
- }
- else if (count == 0) {
- // no addresses
- }
- else {
- address = [NSString stringWithFormat:@"%s", inet_ntoa(in_addrs[0])];
- }
- break;
- }
- case DNS_PTR:
- /* may get at most one PTR */
- // this needs review. TB.
- if (count == 1)
- fprintf(stdout, "addresses: %s ", *(char **)addresses);
- break;
- default:
- break;
- }
- }
- NuBlock *block = (NuBlock *) arg;
- NuCell *args = [[NuCell alloc] init];
- [args setCar:address];
- [block evalWithArguments:args context:nil];
- [block release];
- [args release];
-}
-
-- (void) resolveDomainName:(NSString *) name andDo:(NuBlock *) block
-{
- [block retain];
- evdns_resolve_ipv4([name cStringUsingEncoding:NSUTF8StringEncoding], 0, nunja_dns_gethostbyname_cb, block);
-}
-
-void nunja_http_request_done(struct evhttp_request *req, void *arg)
-{
- fprintf(stdout, "received %d bytes\n", (int) arg);
- NSData *data = nil;
- if (req->response_code != HTTP_OK) {
- if (req->response_code == HTTP_SEEOTHER) {
- fprintf(stdout, "REDIRECTING\n");
- NSDictionary *headers = nunja_request_headers_helper(req);
- return; // this is not handled yet.
- }
- fprintf(stdout, "FAILED to get OK (response = %d)\n", req->response_code);
- }
- else if (evhttp_find_header(req->input_headers, "Content-Type") == NULL) {
- fprintf(stdout, "FAILED to find Content-Type\n");
- }
- else {
- data = [NSData dataWithBytes:EVBUFFER_DATA(req->input_buffer) length:EVBUFFER_LENGTH(req->input_buffer)];
- }
- NuBlock *block = (NuBlock *) arg;
- NuCell *args = [[NuCell alloc] init];
- [args setCar:data];
- [block evalWithArguments:args context:nil];
- [block release];
- [args release];
- fprintf(stdout, "end of callback\n");
- // leaking...
- //evhttp_connection_free(req->evcon);
-}
-
-- (void) getResourceFromHost:(NSString *) host address:(NSString *) address port:(int)port path:(NSString *)path andDo:(NuBlock *) block
-{
- [block retain];
- // make the connection
- struct evhttp_connection *evcon = evhttp_connection_new([address cStringUsingEncoding:NSUTF8StringEncoding], port);
- if (evcon == NULL) {
- fprintf(stdout, "FAILED to connect\n");
- NuCell *args = [[NuCell alloc] init];
- [block evalWithArguments:args context:nil];
- [block release];
- [args release];
- return;
- }
- // make the request
- struct evhttp_request *req = evhttp_request_new(nunja_http_request_done, block);
- evhttp_add_header(req->output_headers, "Host", [host cStringUsingEncoding:NSUTF8StringEncoding]);
- // give ownership of the request to the connection
- if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, [path cStringUsingEncoding:NSUTF8StringEncoding]) == -1) {
- fprintf(stdout, "FAILED to make the request \n");
- }
-}
-
-- (void) postDataToHost:(NSString *) host address:(NSString *) address port:(int)port path:(NSString *)path data:(NSData *) data andDo:(NuBlock *) block
-{
- [block retain];
- // make the connection
- struct evhttp_connection *evcon = evhttp_connection_new([address cStringUsingEncoding:NSUTF8StringEncoding], port);
- if (evcon == NULL) {
- fprintf(stdout, "FAILED to connect\n");
- NuCell *args = [[NuCell alloc] init];
- [block evalWithArguments:args context:nil];
- [block release];
- [args release];
- return;
- }
- // make the request
- struct evhttp_request *req = evhttp_request_new(nunja_http_request_done, block);
- evhttp_add_header(req->output_headers, "Host", [host cStringUsingEncoding:NSUTF8StringEncoding]);
- evhttp_add_header(req->output_headers, "Content-Length", [[NSString stringWithFormat:@"%d", [data length]] cStringUsingEncoding:NSUTF8StringEncoding]);
- evhttp_add_header(req->output_headers, "Content-Type", "application/x-www-form-urlencoded");
- evbuffer_add(req->output_buffer, [data bytes], [data length]);
-
- // give ownership of the request to the connection
- if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, [path cStringUsingEncoding:NSUTF8StringEncoding]) == -1) {
- fprintf(stdout, "FAILED to make the request \n");
- }
-}
-
-@end
diff --git a/objc/passwd.m b/objc/passwd.m
deleted file mode 100644
index 744da6a..0000000
--- a/objc/passwd.m
+++ /dev/null
@@ -1,182 +0,0 @@
-#import "nunja.h"
-
-char *md5_crypt(const char *pw, const char *salt);
-
-@implementation SuperNunja (SaltedPasswords)
-
-+ (NSString *) saltedPassword:(NSString *) password withSalt:(NSString *) salt
-{
- char *passwordString = [password cStringUsingEncoding:NSUTF8StringEncoding];
- char *saltString = [salt cStringUsingEncoding:NSUTF8StringEncoding];
-
- size_t pw_maxlen = 256;
-
- /* truncate password if necessary */
- if ((strlen(passwordString) > pw_maxlen)) {
- passwordString[pw_maxlen] = 0;
- }
-
- /* now compute password hash */
- char *hash = md5_crypt(passwordString, saltString);
-
- return [[NSString alloc] initWithCString:hash encoding:NSUTF8StringEncoding];
-}
-
-@end
-
-////// The remainder of this file is covered by the following license:
-
-/*
- * ----------------------------------------------------------------------------
- * "THE BEER-WARE LICENSE" (Revision 42):
- * wrote this file. As long as you retain this notice you
- * can do whatever you want with this stuff. If we meet some day, and you think
- * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
- * ----------------------------------------------------------------------------
- */
-
-/*
- * Ported from FreeBSD to Linux, only minimal changes. --marekm
- */
-
-/*
- * Adapted from shadow-19990607 by Tudor Bosman, tudorb@jm.nu
- */
-
-#pragma ident "%Z%%M% %I% %E% SMI"
-
-#include
-
-static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */
- "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
-
-static char *magic = "$1$"; /*
- * This string is magic for
- * this algorithm. Having
- * it this way, we can get
- * get better later on
- */
-
-static void
-to64(char *s, unsigned long v, int n)
-{
- while (--n >= 0) {
- *s++ = itoa64[v&0x3f];
- v >>= 6;
- }
-}
-
-int
-is_md5_salt(const char *salt)
-{
- return (!strncmp(salt, magic, strlen(magic)));
-}
-
-/*
- * UNIX password
- *
- * Use MD5 for what it is best at...
- */
-
-char *
-md5_crypt(const char *pw, const char *salt)
-{
- static char passwd[120], *p;
- static const char *sp,*ep;
- unsigned char final[16];
- int sl,pl,i,j;
- MD5_CTX ctx,ctx1;
- unsigned long l;
-
- /* Refine the Salt first */
- sp = salt;
-
- /* If it starts with the magic string, then skip that */
- if(!strncmp(sp,magic,strlen(magic)))
- sp += strlen(magic);
-
- /* It stops at the first '$', max 8 chars */
- for(ep=sp;*ep && *ep != '$' && ep < (sp+8);ep++)
- continue;
-
- /* get the length of the true salt */
- sl = ep - sp;
-
- MD5_Init(&ctx);
-
- /* The password first, since that is what is most unknown */
- MD5_Update(&ctx,pw,strlen(pw));
-
- /* Then our magic string */
- MD5_Update(&ctx,magic,strlen(magic));
-
- /* Then the raw salt */
- MD5_Update(&ctx,sp,sl);
-
- /* Then just as many characters of the MD5(pw,salt,pw) */
- MD5_Init(&ctx1);
- MD5_Update(&ctx1,pw,strlen(pw));
- MD5_Update(&ctx1,sp,sl);
- MD5_Update(&ctx1,pw,strlen(pw));
- MD5_Final(final,&ctx1);
- for(pl = strlen(pw); pl > 0; pl -= 16)
- MD5_Update(&ctx,final,pl>16 ? 16 : pl);
-
- /* Don't leave anything around in vm they could use. */
- memset(final,0,sizeof final);
-
- /* Then something really weird... */
- for (j=0,i = strlen(pw); i ; i >>= 1)
- if(i&1)
- MD5_Update(&ctx, final+j, 1);
- else
- MD5_Update(&ctx, pw+j, 1);
-
- /* Now make the output string */
- strcpy(passwd,magic);
- strncat(passwd,sp,sl);
- strcat(passwd,"$");
-
- MD5_Final(final,&ctx);
-
- /*
- * and now, just to make sure things don't run too fast
- * On a 60 Mhz Pentium this takes 34 msec, so you would
- * need 30 seconds to build a 1000 entry dictionary...
- */
- for(i=0;i<1000;i++) {
- MD5_Init(&ctx1);
- if(i & 1)
- MD5_Update(&ctx1,pw,strlen(pw));
- else
- MD5_Update(&ctx1,final,16);
-
- if(i % 3)
- MD5_Update(&ctx1,sp,sl);
-
- if(i % 7)
- MD5_Update(&ctx1,pw,strlen(pw));
-
- if(i & 1)
- MD5_Update(&ctx1,final,16);
- else
- MD5_Update(&ctx1,pw,strlen(pw));
- MD5_Final(final,&ctx1);
- }
-
- p = passwd + strlen(passwd);
-
- l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(p,l,4); p += 4;
- l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(p,l,4); p += 4;
- l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(p,l,4); p += 4;
- l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(p,l,4); p += 4;
- l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(p,l,4); p += 4;
- l = final[11] ; to64(p,l,2); p += 2;
- *p = '\0';
-
- /* Don't leave anything around in vm they could use. */
- memset(final,0,sizeof final);
-
- return passwd;
-}
-
diff --git a/sample/site.nu b/sample/site.nu
index f2caf7b..6cddc30 100644
--- a/sample/site.nu
+++ b/sample/site.nu
@@ -15,6 +15,47 @@
;; limitations under the License.
(load "NuHTTPHelpers")
+(load "Nu:template")
+
+;; import some useful C functions
+(global random (NuBridgedFunction functionWithName:"random" signature:"l"))
+(global srandom (NuBridgedFunction functionWithName:"srandom" signature:"vI"))
+
+(class NunjaRequest
+ (- post is ((self body) urlQueryDictionary)))
+
+;; @class NunjaCookie
+;; @discussion A class for managing user-identifying cookies.
+(class NunjaCookie is NSObject
+ (ivars)
+ (ivar-accessors)
+
+ ;; Generate a random identifier for use in a cookie.
+ (+ (id) randomIdentifier is
+ "#{((random) stringValue)}#{((random) stringValue)}#{((random) stringValue)}#{((random) stringValue)}")
+
+ ;; Construct a cookie for a specified user.
+ (+ (id) cookieForUser:(id) user is
+ ((self alloc) initWithUser:user
+ value:(self randomIdentifier)
+ expiration:(NSDate dateWithTimeIntervalSinceNow:3600)))
+
+ ;; Initialize a cookie for a specified user.
+ (- (id) initWithUser:(id) user value:(id) value expiration:(id) expiration is
+ (super init)
+ (set @name "session")
+ (set @user user)
+ (set @value value)
+ (set @expiration expiration)
+ (set @stringValue nil)
+ self)
+
+ ;; Get a string description of a cookie.
+ (- (id) description is
+ "cookie=#{@name} value=#{@value} user=#{@user} expiration=#{(@expiration rfc822)}")
+
+ ;; Get a string value for a cookie suitable for inclusion in a response header.
+ (- (id) stringValue is "#{@name}=#{@value}; Expires:#{(@expiration rfc1123)}; Path=/"))
;; global variables
(set sessionCookies (dict))
@@ -88,7 +129,7 @@ HTML)) forKey:"BODY")
(set post (REQUEST post))
(if (eq (post "response") "Cancel")
(then
- (Nunja redirectResponse:REQUEST toLocation:"/"))
+ (REQUEST redirectResponseToLocation:"/"))
(else
(set username (post "username"))
(set password (post "password"))
@@ -97,7 +138,7 @@ HTML)) forKey:"BODY")
(set sessionCookie (NunjaCookie cookieForUser:username))
(sessionCookies setObject:sessionCookie forKey:(sessionCookie value))
(REQUEST setValue:(sessionCookie stringValue) forResponseHeader:"Set-Cookie")
- (Nunja redirectResponse:REQUEST toLocation:"/"))
+ (REQUEST redirectResponseToLocation:"/"))
(else
(RESPONSE setValue:"Please try again" forKey:"TITLE")
(RESPONSE setValue:(eval (NuTemplate codeForString:<<-HTML
@@ -115,7 +156,7 @@ HTML)) forKey:"BODY")
(get "/logout"
(set sessionCookieName ((REQUEST cookies) "session"))
(if sessionCookieName (sessionCookies removeObjectForKey:sessionCookieName))
- (Nunja redirectResponse:REQUEST toLocation:"/"))
+ (REQUEST redirectResponseToLocation:"/"))
;; add-a-friend page.
(get "/addfriend"
@@ -139,13 +180,13 @@ HTML)) forKey:"BODY")
(set post (REQUEST post))
(if (eq (post "response") "Submit")
(friends << (dict name:(post "name") email:(post "email"))))
- (Nunja redirectResponse:REQUEST toLocation:"/"))
+ (REQUEST redirectResponseToLocation:"/"))
;; delete-a-friend with a GET. Strictly, this should be a post, but we use a get to show how it would be done.
(get "/delete"
(set post (REQUEST query))
(set friends (friends select:(do (friend) (!= (friend "name") (post "name")))))
- (Nunja redirectResponse:REQUEST toLocation:"/"))
+ (REQUEST redirectResponseToLocation:"/"))
(get "/about"
(set RESPONSE (dict))
@@ -186,9 +227,9 @@ END forKey:"BODY")
(eval page-layout))
;; large file download
-(get (regex -"/data(.*)")
+(get "/data/size:"
(REQUEST setValue:"application/octet-stream" forResponseHeader:"Content-Type")
- (set size ((REQUEST match) groupAtIndex:1))
+ (set size ((REQUEST bindings) size:))
(set megabytes (if (eq size "")
then 1
else (size doubleValue)))
@@ -196,8 +237,8 @@ END forKey:"BODY")
;; perform a dns lookup
;; ex: /dns/programming.nu
-(get (regex -"/dns/(.*)")
- (set hostname ((REQUEST match) groupAtIndex:1))
+(get "/dns/hostname:"
+ (set hostname ((REQUEST bindings) hostname:))
((REQUEST nunja) resolveDomainName:hostname andDo:
(do (address)
(if address
@@ -206,23 +247,25 @@ END forKey:"BODY")
(REQUEST respondWithString:"unable to resolve #{hostname}"))))
nil) ;; return nil to leave the connection open
-;; request and return a resource from a specified host
-;; ex: /proxy/programming.nu/about
-(get (regex -"/proxy/([^\/]+)/(.*)")
- (set host ((REQUEST match) groupAtIndex:1))
- (set path (+ "/" ((REQUEST match) groupAtIndex:2)))
- ((REQUEST nunja) resolveDomainName:host andDo:
- (do (address)
- (if address
- (then ((REQUEST nunja) getResourceFromHost:host address:address port:80 path:path andDo:
- (do (data)
- (if data
- (then (REQUEST respondWithData:data))
- (else (REQUEST respondWithString:"unable to load #{path}"))))))
- (else (REQUEST respondWithString:"unable to resolve host #{host}")))))
- nil) ;; return nil to leave the connection open
+(if NO
+ ;; request and return a resource from a specified host
+ ;; ex: /proxy/programming.nu/about
+ (get (regex -"/proxy/([^\/]+)/(.*)")
+ (set host ((REQUEST match) groupAtIndex:1))
+ (set path (+ "/" ((REQUEST match) groupAtIndex:2)))
+ ((REQUEST nunja) resolveDomainName:host andDo:
+ (do (address)
+ (if address
+ (then ((REQUEST nunja) getResourceFromHost:host address:address port:80 path:path andDo:
+ (do (data)
+ (if data
+ (then (REQUEST respondWithData:data))
+ (else (REQUEST respondWithString:"unable to load #{path}"))))))
+ (else (REQUEST respondWithString:"unable to resolve host #{host}")))))
+ nil) ;; return nil to leave the connection open
+ )
-(get (regex -"/posttest")
+(get "/posttest"
(set host "localhost")
(set path "/login")
((REQUEST nunja) resolveDomainName:host andDo:
@@ -252,19 +295,16 @@ END forKey:"BODY")
(get "/recycle.ico"
(REQUEST setValue:"application/icon" forResponseHeader:"Content-Type")
- (NSData dataWithContentsOfFile:"sample/public/favicon.ico"))
+ (NSData dataWithContentsOfFile:"public/favicon.ico"))
-(get "/follow/:me"
+(get "/follow/me:"
(REQUEST setValue:"text/plain" forResponseHeader:"Content-Type")
(+ "/follow/" ((REQUEST bindings) "me")))
-(get "/:a/before/:b"
+(get "/a:/before/b:"
(REQUEST setValue:"text/plain" forResponseHeader:"Content-Type")
(+ "/" ((REQUEST bindings) "b") "/after/" ((REQUEST bindings) "a")))
-(get (regex -"^/foo/([^/]+)$")
- ((REQUEST match) groupAtIndex:1))
-
(get "/get"
(set q (REQUEST query))
(set a (((q allKeys) sort) map:
@@ -279,6 +319,7 @@ END forKey:"BODY")
(+ key ":" (q key)))))
(a componentsJoinedByString:","))
-
+(get-404
+ "Resource Not Found: #{(REQUEST path)}")
diff --git a/test/test_markup.nu b/test/test_markup.nu
deleted file mode 100644
index 0fa44fd..0000000
--- a/test/test_markup.nu
+++ /dev/null
@@ -1,19 +0,0 @@
-;; test_markup.nu
-;; tests for Nunja markup operator
-;;
-;; Copyright (c) 2010 Tim Burks, Neon Design Technology, Inc.
-
-(load "Nunja")
-
-(class TestMarkup is NuTestCase
-
- (- testMarkup is
- (set &html (NunjaMarkupOperator operatorWithTag:"html" prefix:"\n"))
- (set &body (NunjaMarkupOperator operatorWithTag:"body"))
-
- (assert_equal "" (&body))
- (assert_equal "" (&body incomplete:))
- (assert_equal "" (&body attr:"val"))
- (assert_equal "\nhello, world" (&html (&body this:"is" a:"test" "hello," " world")))
- (assert_equal "abc123" (&body (array "a" "b" "c") 123))
- (assert_equal "12." (&body "1" (if (eq 1 1) 2) (if (eq 1 2) 3) "."))))
diff --git a/test/test_salt.nu b/test/test_salt.nu
deleted file mode 100644
index 2e2dd56..0000000
--- a/test/test_salt.nu
+++ /dev/null
@@ -1,15 +0,0 @@
-;; test_salt.nu
-;; tests for Nunja password salting helper.
-;;
-;; Copyright (c) 2007 Tim Burks, Neon Design Technology, Inc.
-
-(load "Nunja")
-
-(class TestSalt is NuTestCase
-
- (- testSalt is
- (set salted (Nunja saltedPassword:"secret" withSalt:"sauce"))
- ;; golden result obtained with "openssl passwd -1 -salt sauce"
- (assert_equal "$1$sauce$ToKwxvX1ZyeiswSSzdPRi0" salted)))
-
-