diff --git a/.gitignore b/.gitignore index d697c7f..0a734d7 100644 --- a/.gitignore +++ b/.gitignore @@ -36,5 +36,6 @@ Carthage/ # Apous bin/ -.apous.swift - +.apousscript +VersionInfo.swift +releases/ diff --git a/Features.md b/Features.md new file mode 100644 index 0000000..409732b --- /dev/null +++ b/Features.md @@ -0,0 +1,9 @@ +Features Planned for v0.2.0: + + 1. Refactoring project to support unit tests + 2. Support nested directories for the script + +Possible for v0.2.0: + + 1. Creation of implicit Xcode file for Xcode authoring support + \ No newline at end of file diff --git a/README.md b/README.md index 011a172..b50996c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ |::.|:. | `--- ---' +# Apous [![GitHub license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://raw.githubusercontent.com/owensd/apous/master/LICENSE) [![GitHub release](https://img.shields.io/github/release/owensd/apous.svg)](https://github.com/owensd/Apous/releases) + + Apous is a simple tool that allows for easier authoring of Swift scripts. Primary features: @@ -13,37 +16,37 @@ Primary features: 1. Allow the breaking up of scripts into multiple files. 2. Dependency management through [Carthage](https://github.com/Carthage/Carthage) or [CocoaPods](https://github.com/CocoaPods/CocoaPods/). -# How it Works +## How it Works Apous works by first checking for a `Cartfile` or `Podfile` in your script's directory. If one is present, then `carthage update` or `pod install --no-integrate` will be run. -Next, all of your Swift files are combined into a single `.apous.swift` file that can -then be run by the `swift` REPL. +Next, all of your Swift files are compiled into a single `.apousscript` binary that will then be +run automatically for you. It's really that simple. -# Getting Started +## Getting Started First, you need to install the latest build of Apous. 1. Download the latest version of `apous` from "Releases". 2. Copy it to a location in your path, such as `/usr/local/bin/`. -# Creating Your First Script +## Creating Your First Script 1. Create a new directory for your scripts, say `mkdir demo` 2. Change to that directory: `cd demo` 3. Create a new script file: `touch demo.swift` 4. Change the contents of the file to: -```swift -import Foundation + ```swift + import Foundation -print("Welcome to Apous!") -``` + print("Welcome to Apous!") + ``` -5. Run the script: `apous demo.swift` +5. Run the script: `apous .` This will output: @@ -51,15 +54,27 @@ This will output: You can see some other samples here: [Samples](https://github.com/owensd/apous/tree/master/samples). +### Alternatively + +Apous also supports running scripts with `#!`. Note that your entry point script **must** be named `main.swift`. + +```swift +#!/usr/local/bin/apous + +import Foundation + +print("Welcome to Apous!") +``` -# Known Issues +Then run: + + > chmod +x main.swift + > ./main.swift + Welcome to Apous! -Currently there are some design limitations: - * [Issue #1](https://github.com/owensd/apous/issues/1) - Support for nested directories. - * [Issue #2](https://github.com/owensd/apous/issues/2) - Support for folder structure packages. +## FAQ -# FAQ +**Q: What is Apous mean?** -Q: What is Apous mean? A: It's from the ancient Greek απους, meaning "without feet". diff --git a/apous.xcodeproj/project.pbxproj b/apous.xcodeproj/project.pbxproj index 6f50c3e..7c9ba22 100644 --- a/apous.xcodeproj/project.pbxproj +++ b/apous.xcodeproj/project.pbxproj @@ -8,12 +8,34 @@ /* Begin PBXBuildFile section */ 7D0373391B49021700E2711D /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D0373381B49021700E2711D /* main.swift */; }; - 7D0373401B49028E00E2711D /* apous in Deploy Locally */ = {isa = PBXBuildFile; fileRef = 7D0373351B49021700E2711D /* apous */; }; + 7D0F00941B4AF32F003B6EF0 /* Samples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D0F00931B4AF32F003B6EF0 /* Samples.swift */; }; 7D3AC3461B49F99B0068CC83 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D3AC3451B49F99B0068CC83 /* Utils.swift */; }; 7D3AC3481B49FE170068CC83 /* ErrorCodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D3AC3471B49FE170068CC83 /* ErrorCodes.swift */; }; 7D3AC34A1B4A37BC0068CC83 /* Tools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D3AC3491B4A37BC0068CC83 /* Tools.swift */; }; + 7DD671141B4BD94400C5DB6D /* apous in Deploy Locally */ = {isa = PBXBuildFile; fileRef = 7D0373351B49021700E2711D /* apous */; }; + 7DF997441B4B33A200E90F56 /* VersionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DF997431B4B33A200E90F56 /* VersionInfo.swift */; }; + 7DF997511B4B4A6900E90F56 /* Tools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D3AC3491B4A37BC0068CC83 /* Tools.swift */; }; + 7DF9988C1B4B72FD00E90F56 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D3AC3451B49F99B0068CC83 /* Utils.swift */; }; + 7DF9988D1B4B730500E90F56 /* ErrorCodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D3AC3471B49FE170068CC83 /* ErrorCodes.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 7DF9974B1B4B354000E90F56 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7D03732D1B49021700E2711D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7DF997471B4B353600E90F56; + remoteInfo = GenerateVersionInfo; + }; + 7DF9974D1B4B354800E90F56 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7D03732D1B49021700E2711D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7DF997471B4B353600E90F56; + remoteInfo = GenerateVersionInfo; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 7D0373331B49021700E2711D /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; @@ -30,7 +52,7 @@ dstPath = "$(SRCROOT)/bin"; dstSubfolderSpec = 0; files = ( - 7D0373401B49028E00E2711D /* apous in Deploy Locally */, + 7DD671141B4BD94400C5DB6D /* apous in Deploy Locally */, ); name = "Deploy Locally"; runOnlyForDeploymentPostprocessing = 0; @@ -40,11 +62,18 @@ /* Begin PBXFileReference section */ 7D0373351B49021700E2711D /* apous */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = apous; sourceTree = BUILT_PRODUCTS_DIR; }; 7D0373381B49021700E2711D /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 7D0F00911B4AF32F003B6EF0 /* apoustest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = apoustest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 7D0F00931B4AF32F003B6EF0 /* Samples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Samples.swift; sourceTree = ""; }; + 7D0F00951B4AF32F003B6EF0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7D0F009A1B4AF3F1003B6EF0 /* Features.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Features.md; sourceTree = ""; }; 7D3AC3421B49F1FC0068CC83 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 7D3AC3441B49F62F0068CC83 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 7D3AC3451B49F99B0068CC83 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 7D3AC3471B49FE170068CC83 /* ErrorCodes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorCodes.swift; sourceTree = ""; }; 7D3AC3491B4A37BC0068CC83 /* Tools.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tools.swift; sourceTree = ""; }; + 7DC546B91B4C55640070A858 /* samples */ = {isa = PBXFileReference; lastKnownFileType = folder; path = samples; sourceTree = SOURCE_ROOT; }; + 7DF997431B4B33A200E90F56 /* VersionInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionInfo.swift; sourceTree = ""; }; + 7DF9974F1B4B356700E90F56 /* version.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = version.sh; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -55,15 +84,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7D0F008E1B4AF32F003B6EF0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 7D03732C1B49021700E2711D = { isa = PBXGroup; children = ( - 7D3AC3441B49F62F0068CC83 /* LICENSE */, - 7D3AC3421B49F1FC0068CC83 /* README.md */, + 7D0F00991B4AF3D9003B6EF0 /* docs */, 7D0373371B49021700E2711D /* src */, + 7D0F00921B4AF32F003B6EF0 /* test */, 7D0373361B49021700E2711D /* Products */, ); sourceTree = ""; @@ -72,6 +108,7 @@ isa = PBXGroup; children = ( 7D0373351B49021700E2711D /* apous */, + 7D0F00911B4AF32F003B6EF0 /* apoustest.xctest */, ); name = Products; sourceTree = ""; @@ -79,6 +116,7 @@ 7D0373371B49021700E2711D /* src */ = { isa = PBXGroup; children = ( + 7DF997501B4B37B100E90F56 /* Versioning */, 7D0373381B49021700E2711D /* main.swift */, 7D3AC3451B49F99B0068CC83 /* Utils.swift */, 7D3AC3471B49FE170068CC83 /* ErrorCodes.swift */, @@ -87,8 +125,54 @@ path = src; sourceTree = ""; }; + 7D0F00921B4AF32F003B6EF0 /* test */ = { + isa = PBXGroup; + children = ( + 7DC546B91B4C55640070A858 /* samples */, + 7D0F00931B4AF32F003B6EF0 /* Samples.swift */, + 7D0F00951B4AF32F003B6EF0 /* Info.plist */, + ); + path = test; + sourceTree = ""; + }; + 7D0F00991B4AF3D9003B6EF0 /* docs */ = { + isa = PBXGroup; + children = ( + 7D0F009A1B4AF3F1003B6EF0 /* Features.md */, + 7D3AC3441B49F62F0068CC83 /* LICENSE */, + 7D3AC3421B49F1FC0068CC83 /* README.md */, + ); + name = docs; + sourceTree = ""; + }; + 7DF997501B4B37B100E90F56 /* Versioning */ = { + isa = PBXGroup; + children = ( + 7DF997431B4B33A200E90F56 /* VersionInfo.swift */, + 7DF9974F1B4B356700E90F56 /* version.sh */, + ); + name = Versioning; + sourceTree = ""; + }; /* End PBXGroup section */ +/* Begin PBXLegacyTarget section */ + 7DF997471B4B353600E90F56 /* GenerateVersionInfo */ = { + isa = PBXLegacyTarget; + buildArgumentsString = version.sh; + buildConfigurationList = 7DF997481B4B353600E90F56 /* Build configuration list for PBXLegacyTarget "GenerateVersionInfo" */; + buildPhases = ( + ); + buildToolPath = /bin/sh; + buildWorkingDirectory = "$(SRCROOT)/src"; + dependencies = ( + ); + name = GenerateVersionInfo; + passBuildSettingsInEnvironment = 1; + productName = GenerateVersionInfo; + }; +/* End PBXLegacyTarget section */ + /* Begin PBXNativeTarget section */ 7D0373341B49021700E2711D /* apous */ = { isa = PBXNativeTarget; @@ -98,16 +182,37 @@ 7D0373321B49021700E2711D /* Frameworks */, 7D0373331B49021700E2711D /* CopyFiles */, 7D03733F1B49026800E2711D /* Deploy Locally */, + 7DE115081B4C4A9100B198FB /* Package for GitHub Release */, ); buildRules = ( ); dependencies = ( + 7DF9974C1B4B354000E90F56 /* PBXTargetDependency */, ); name = apous; productName = apous; productReference = 7D0373351B49021700E2711D /* apous */; productType = "com.apple.product-type.tool"; }; + 7D0F00901B4AF32F003B6EF0 /* apoustest */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7D0F00981B4AF32F003B6EF0 /* Build configuration list for PBXNativeTarget "apoustest" */; + buildPhases = ( + 7D0F008D1B4AF32F003B6EF0 /* Sources */, + 7D0F008E1B4AF32F003B6EF0 /* Frameworks */, + 7D0F008F1B4AF32F003B6EF0 /* Resources */, + 7DF9988B1B4B4C5600E90F56 /* Copy Samples */, + ); + buildRules = ( + ); + dependencies = ( + 7DF9974E1B4B354800E90F56 /* PBXTargetDependency */, + ); + name = apoustest; + productName = apoustest; + productReference = 7D0F00911B4AF32F003B6EF0 /* apoustest.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -120,6 +225,12 @@ 7D0373341B49021700E2711D = { CreatedOnToolsVersion = 7.0; }; + 7D0F00901B4AF32F003B6EF0 = { + CreatedOnToolsVersion = 7.0; + }; + 7DF997471B4B353600E90F56 = { + CreatedOnToolsVersion = 7.0; + }; }; }; buildConfigurationList = 7D0373301B49021700E2711D /* Build configuration list for PBXProject "apous" */; @@ -134,11 +245,55 @@ projectDirPath = ""; projectRoot = ""; targets = ( + 7DF997471B4B353600E90F56 /* GenerateVersionInfo */, 7D0373341B49021700E2711D /* apous */, + 7D0F00901B4AF32F003B6EF0 /* apoustest */, ); }; /* End PBXProject section */ +/* Begin PBXResourcesBuildPhase section */ + 7D0F008F1B4AF32F003B6EF0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 7DE115081B4C4A9100B198FB /* Package for GitHub Release */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Package for GitHub Release"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "mkdir -p releases\nrm -f $SRCROOT/releases/$TARGET_NAME.zip\nzip -j $SRCROOT/releases/$TARGET_NAME.zip $SRCROOT/bin/$TARGET_NAME"; + }; + 7DF9988B1B4B4C5600E90F56 /* Copy Samples */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/samples", + ); + name = "Copy Samples"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "rm -rf \"$TARGET_BUILD_DIR/samples\"\ncp -R \"$SRCROOT/samples\" \"$TARGET_BUILD_DIR\""; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 7D0373311B49021700E2711D /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -147,12 +302,37 @@ 7D3AC3461B49F99B0068CC83 /* Utils.swift in Sources */, 7D3AC3481B49FE170068CC83 /* ErrorCodes.swift in Sources */, 7D0373391B49021700E2711D /* main.swift in Sources */, + 7DF997441B4B33A200E90F56 /* VersionInfo.swift in Sources */, 7D3AC34A1B4A37BC0068CC83 /* Tools.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + 7D0F008D1B4AF32F003B6EF0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7D0F00941B4AF32F003B6EF0 /* Samples.swift in Sources */, + 7DF997511B4B4A6900E90F56 /* Tools.swift in Sources */, + 7DF9988C1B4B72FD00E90F56 /* Utils.swift in Sources */, + 7DF9988D1B4B730500E90F56 /* ErrorCodes.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 7DF9974C1B4B354000E90F56 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 7DF997471B4B353600E90F56 /* GenerateVersionInfo */; + targetProxy = 7DF9974B1B4B354000E90F56 /* PBXContainerItemProxy */; + }; + 7DF9974E1B4B354800E90F56 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 7DF997471B4B353600E90F56 /* GenerateVersionInfo */; + targetProxy = 7DF9974D1B4B354800E90F56 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ 7D03733A1B49021700E2711D /* Debug */ = { isa = XCBuildConfiguration; @@ -172,7 +352,6 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0.1.1; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -216,7 +395,6 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0.1.1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -248,6 +426,51 @@ }; name = Release; }; + 7D0F00961B4AF32F003B6EF0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = test/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = io.owensd.apoustest; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 7D0F00971B4AF32F003B6EF0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = test/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = io.owensd.apoustest; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 7DF997491B4B353600E90F56 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEBUGGING_SYMBOLS = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 7DF9974A1B4B353600E90F56 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -269,6 +492,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 7D0F00981B4AF32F003B6EF0 /* Build configuration list for PBXNativeTarget "apoustest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7D0F00961B4AF32F003B6EF0 /* Debug */, + 7D0F00971B4AF32F003B6EF0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7DF997481B4B353600E90F56 /* Build configuration list for PBXLegacyTarget "GenerateVersionInfo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7DF997491B4B353600E90F56 /* Debug */, + 7DF9974A1B4B353600E90F56 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 7D03732D1B49021700E2711D /* Project object */; diff --git a/samples/carthage/Cartfile.resolved b/samples/carthage/Cartfile.resolved deleted file mode 100644 index febe3ab..0000000 --- a/samples/carthage/Cartfile.resolved +++ /dev/null @@ -1,2 +0,0 @@ -github "thoughtbot/Runes" "3a46bd92d78061c76c08a0b2baf6ee214dba77e9" -github "thoughtbot/Argo" "5b18ce0da13e3e6d8a3a5188cbf00bd8d5c86a0c" diff --git a/samples/carthage/main.swift b/samples/carthage/main.swift index 073312b..fdfbe84 100644 --- a/samples/carthage/main.swift +++ b/samples/carthage/main.swift @@ -1,4 +1,4 @@ import Argo import Runes -print("dependencies import properly") \ No newline at end of file +print("dependencies imported properly") \ No newline at end of file diff --git a/samples/cocoapods/Podfile.lock b/samples/cocoapods/Podfile.lock deleted file mode 100644 index ac23939..0000000 --- a/samples/cocoapods/Podfile.lock +++ /dev/null @@ -1,23 +0,0 @@ -PODS: - - Argo (1.0.3): - - Runes (>= 1.2.2) - - Runes (2.0.0) - -DEPENDENCIES: - - Argo (from `https://github.com/thoughtbot/Argo`, branch `td-swift-2`) - -EXTERNAL SOURCES: - Argo: - :branch: td-swift-2 - :git: https://github.com/thoughtbot/Argo - -CHECKOUT OPTIONS: - Argo: - :commit: 5b18ce0da13e3e6d8a3a5188cbf00bd8d5c86a0c - :git: https://github.com/thoughtbot/Argo - -SPEC CHECKSUMS: - Argo: 541f7b9167f264ddf9c6c5d75a87e8c68e29929d - Runes: 4fe81355f4620b76b02176222d264b33e60dba51 - -COCOAPODS: 0.38.0.beta.2 diff --git a/samples/cocoapods/main.swift b/samples/cocoapods/main.swift index 073312b..fdfbe84 100644 --- a/samples/cocoapods/main.swift +++ b/samples/cocoapods/main.swift @@ -1,4 +1,4 @@ import Argo import Runes -print("dependencies import properly") \ No newline at end of file +print("dependencies imported properly") \ No newline at end of file diff --git a/samples/nested/main.swift b/samples/nested/main.swift new file mode 100644 index 0000000..521bb34 --- /dev/null +++ b/samples/nested/main.swift @@ -0,0 +1,4 @@ + +print("Testing Nested Directories") +print("abspath: \(abspath())") +print("basename: \(basename())") diff --git a/samples/nested/os/path/abspath.swift b/samples/nested/os/path/abspath.swift new file mode 100644 index 0000000..30227df --- /dev/null +++ b/samples/nested/os/path/abspath.swift @@ -0,0 +1,4 @@ + +func abspath() -> String { + return "abspath!" +} \ No newline at end of file diff --git a/samples/nested/os/path/basename.swift b/samples/nested/os/path/basename.swift new file mode 100644 index 0000000..fbdca0a --- /dev/null +++ b/samples/nested/os/path/basename.swift @@ -0,0 +1,4 @@ + +func basename() -> String { + return "basename!" +} \ No newline at end of file diff --git a/samples/shebang/main.swift b/samples/shebang/main.swift new file mode 100755 index 0000000..121e1f3 --- /dev/null +++ b/samples/shebang/main.swift @@ -0,0 +1,3 @@ +#!/usr/local/bin/apous + +print("Hello! This is a simple sample that contains no dependencies.") diff --git a/src/ErrorCodes.swift b/src/ErrorCodes.swift index 0f52989..fcba835 100644 --- a/src/ErrorCodes.swift +++ b/src/ErrorCodes.swift @@ -12,4 +12,5 @@ enum ErrorCode: Int, ErrorType { case CarthageNotInstalled case CocoaPodsNotInstalled case SwiftNotInstalled + case PTYCreationFailed } diff --git a/src/Tools.swift b/src/Tools.swift index 9518c68..3350939 100644 --- a/src/Tools.swift +++ b/src/Tools.swift @@ -8,175 +8,176 @@ import Foundation -/// The base abstraction for all command-line utilities. -protocol Tool { - - /// The name of the tool to be run. - var launchPath: String { get } - - /// `true` when the output of the tool should be printed to `stdout`. - var printOutput: Bool { get } - - /// Runs the tool and returns result of the execution. - func run(args: String...) -> (out: String, err: String, code: Int32) -} +// I'm playing around with using functions as the types here because that's all +// that is really needed. The types with a protocol extension in the previous +// version just felt like way too much for what was really needed, which is +// essentially just a way to invoke the tool with some arguments. + +/// The output type for a `Tool`. +typealias TaskResult = (out: String,code: Int32) + +/// The function signature that all tools must conform to. +typealias Tool = (args: String...) throws -> TaskResult + +/// Runs the given tool at `launchPath` passing in `args`. The output is then captured +/// by `output` and `error`. +func runTask(launchPath: String, args: [String] = [], outputToStandardOut: Bool = true) throws -> TaskResult +{ + // This is the buffered output that will be returned. + var out = "" + +// It turns out this code is not robust; it does not seem to always get all of the stream data. +// BUG #12 - https://github.com/owensd/apous/issues/12 +// +// // Ok, so stdout sucks the big one. If your NSTask actually does any redirection to another +// // tool that then outputs to stdout, that is going to be buffered and will only come back in +// // chunks. +// +// var master: Int32 = 0 +// var slave: Int32 = 0 +// if openpty(&master, &slave, nil, nil, nil) == -1 { +// throw ErrorCode.PTYCreationFailed +// } +// defer { +// close(master) +// close(slave) +// } +// +// let output = NSFileHandle(fileDescriptor: master) -extension Tool { - var printOutput: Bool { return true } + func stream(handle: NSFileHandle) -> String { + let data = handle.availableData + let str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String ?? "" - func run(args: String...) -> (out: String, err: String, code: Int32) { - let output = NSPipe() - let error = NSPipe() + // Sample string might look like this: ^[[34;1m***^[[0m Fetching ^[[1mArgo^[[0m + // However, we need the escape codes to look like this: \e[34;1m***\e[0m Fetching \e[1mArgo\e[0m + // Also, the normal output logged needs to have all of that stripped... - // NOTE(owensd): This merges stdout and stderr for now... - func stream(handle: NSFileHandle) -> String { - let data = handle.availableData - let str = NSString(data: data, encoding: NSUTF8StringEncoding) ?? "" + func process(input: String, fn: (inout string: String, range: Range) -> ()) -> String { + var str = input - if self.printOutput { - print("\(str)", appendNewline: false) - } + var range: Range? = nil + var nextRange: Range? = nil + repeat { + range = str.rangeOfString( + "(\\^)\\[\\[(\\d+;\\d+m|\\d+m)", + options: NSStringCompareOptions.RegularExpressionSearch, + range: nextRange) + + if let range = range { + nextRange?.startIndex = range.endIndex + fn(string: &str, range: range) + } + } while range != nil - return str as String + return str + } + + let replaced = process(str) { (inout string: String, range: Range) in + string.replaceRange(range.startIndex ..< advance(range.startIndex, 2), with: "\\e") } - var out = "" - var err = "" - - output.fileHandleForReading.readabilityHandler = { out += stream($0) } - error.fileHandleForReading.readabilityHandler = { err += stream($0) } - - let task = NSTask() - task.launchPath = self.launchPath - task.arguments = args - task.standardOutput = output - task.standardError = error - task.terminationHandler = { - ($0.standardOutput as? NSFileHandle)?.readabilityHandler = nil - ($0.standardError as? NSFileHandle)?.readabilityHandler = nil + let stripped = process(str) { (inout string: String, range: Range) in + string.removeRange(range) } - task.launch() - task.waitUntilExit() + if outputToStandardOut { + // NOTE(owensd): Without the -n, additional newlines are getting in there... + NSTask.launchedTaskWithLaunchPath("/bin/bash", arguments: ["-c", "echo -en $'\(replaced)'"]) + } - return ( - out: out.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()), - err: err.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()), - code: task.terminationStatus) + return stripped } + let output = NSPipe() + + output.fileHandleForReading.readabilityHandler = { out += stream($0) } + + let task = NSTask() + task.launchPath = try canonicalPath(launchPath) + task.arguments = args + task.standardOutput = output + task.standardError = output + task.terminationHandler = { + ($0.standardOutput as? NSFileHandle)?.readabilityHandler = nil + } + + task.launch() + task.waitUntilExit() + + return ( + out: out.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()), + code: task.terminationStatus) } -struct WhichTool : Tool { - let printOutput = false - let launchPath = "/usr/bin/which" +// This is my pseudo-namespace thing that I'm trying out for now... +struct tools { + private init() { fatalError("Can't you see, this is a namespace!") } } -struct CocoaPodsTool: Tool { - let launchPath: String +private func launchPathForTool(tool: String) throws -> String? { + let result = try tools.which(tool) + return result.out.characters.count == 0 ? nil : result.out +} - init?() { - let which = WhichTool() - let result = which.run("pod") - if result.out.characters.count == 0 { return nil } - self.launchPath = result.out +extension tools { + static func which(args: String...) throws -> TaskResult { + return try runTask("/usr/bin/which", args: args, outputToStandardOut: false) } + + static func pod(args: String...) throws -> TaskResult { + guard let path = try launchPathForTool("pod") else { throw ErrorCode.CocoaPodsNotInstalled } - // HACK(owensd): I cannot figure out why this tool will not flush our to stdout in real-time, - // so forcing it to write to stdout for now. - func run(args: String...) -> (out: String, err: String, code: Int32) { - let output = NSFileHandle.fileHandleWithStandardOutput() - let error = NSFileHandle.fileHandleWithStandardError() - - var out = "" - var err = "" - - func stream(handle: NSFileHandle) -> String { - let data = handle.availableData - let str = NSString(data: data, encoding: NSUTF8StringEncoding) ?? "" - return str as String - } - - // NOTE(owensd): These don't work for stdout and stderr... - output.readabilityHandler = { out += stream($0) } - error.readabilityHandler = { err += stream($0) } - - let task = NSTask() - task.launchPath = self.launchPath - task.arguments = args - task.standardOutput = output - task.standardError = error - task.terminationHandler = { - ($0.standardOutput as? NSFileHandle)?.readabilityHandler = nil - ($0.standardError as? NSFileHandle)?.readabilityHandler = nil - } + let info = args.reduce("") { $0 + $1 + " " } + print("Running pod \(info)") + return try runTask(path, args: args) + } + + static func carthage(args: String...) throws -> TaskResult { + guard let path = try launchPathForTool("carthage") else { throw ErrorCode.CarthageNotInstalled } + + let info = args.reduce("") { $0 + $1 + " " } + print("Running carthage \(info)") + return try runTask(path, args: args) + } - task.launch() - task.waitUntilExit() + static func swiftc(args: [String]) throws -> TaskResult { + guard let path = try launchPathForTool("swiftc") else { throw ErrorCode.SwiftNotInstalled } + return try runTask(path, args: args) + } - return ( - out: out.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()), - err: err.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()), - code: task.terminationStatus) + static func swiftc(args: String...) throws -> TaskResult { + return try tools.swiftc(args) } } -struct CarthageTool : Tool { - let launchPath: String - - init?() { - let which = WhichTool() - let result = which.run("carthage") - if result.out.characters.count == 0 { return nil } - self.launchPath = result.out - } +extension tools { + static let CartfileConfig = "Cartfile" + static let PodfileConfig = "Podfile" - // HACK(owensd): I cannot figure out why this tool will not flush our to stdout in real-time, - // so forcing it to write to stdout for now. - func run(args: String...) -> (out: String, err: String, code: Int32) { - let output = NSFileHandle.fileHandleWithStandardOutput() - let error = NSFileHandle.fileHandleWithStandardError() + static func apous(path: String) throws -> TaskResult { + let fileManager = NSFileManager.defaultManager() - var out = "" - var err = "" + // The tools need to be run under the context of the script directory. + fileManager.changeCurrentDirectoryPath(path) - func stream(handle: NSFileHandle) -> String { - let data = handle.availableData - let str = NSString(data: data, encoding: NSUTF8StringEncoding) ?? "" - return str as String - } + var frameworkPaths: [String] = [] - // NOTE(owensd): These don't work for stdout and stderr... - output.readabilityHandler = { out += stream($0) } - error.readabilityHandler = { err += stream($0) } + if fileManager.fileExistsAtPath(path.stringByAppendingPathComponent(CartfileConfig)) { + try tools.carthage("update") + frameworkPaths += ["-F", "Carthage/Build/Mac"] + } - let task = NSTask() - task.launchPath = self.launchPath - task.arguments = args - task.standardOutput = output - task.standardError = error - task.terminationHandler = { - ($0.standardOutput as? NSFileHandle)?.readabilityHandler = nil - ($0.standardError as? NSFileHandle)?.readabilityHandler = nil + if fileManager.fileExistsAtPath(path.stringByAppendingPathComponent(PodfileConfig)) { + try tools.pod("install", "--no-integrate") + frameworkPaths += ["-F", "Rome"] } - task.launch() - task.waitUntilExit() + let files = filesAtPath(path) + let args = frameworkPaths + ["-o", ".apousscript"] + files - return ( - out: out.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()), - err: err.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()), - code: task.terminationStatus) + try tools.swiftc(args) + return try runTask("./.apousscript") } } -struct SwiftTool : Tool { - let launchPath: String - - init?() { - let which = WhichTool() - let result = which.run("swift") - if result.out.characters.count == 0 { return nil } - self.launchPath = result.out - } -} diff --git a/src/Utils.swift b/src/Utils.swift index e31b16b..4316025 100644 --- a/src/Utils.swift +++ b/src/Utils.swift @@ -9,30 +9,14 @@ import Foundation /// Returns the root path that contains the script(s). -/// This is -func canonicalPath(item: String) throws -> String { - func extract() -> String { - if item.pathExtension == "swift" { - let path = item.stringByDeletingLastPathComponent - if path == "" { - return NSFileManager.defaultManager().currentDirectoryPath - } - - return path - } - else { - return item - } - } - - let path = extract().stringByStandardizingPath +func canonicalPath(path: String) throws -> String { + guard let cpath = path.cStringUsingEncoding(NSUTF8StringEncoding) else { throw ErrorCode.PathNotFound } - var isDirectory: ObjCBool = false - if NSFileManager.defaultManager().fileExistsAtPath(path, isDirectory: &isDirectory) { - return path - } + let rpath = realpath(cpath, nil) + if rpath == nil { throw ErrorCode.PathNotFound } - throw ErrorCode.PathNotFound + guard let abspath = String(CString: rpath, encoding: NSUTF8StringEncoding) else { throw ErrorCode.PathNotFound } + return abspath } /// Exit the process error with the given `ErrorCode`. @@ -42,22 +26,14 @@ func canonicalPath(item: String) throws -> String { /// Returns the full path of the valid script files at the given `path`. func filesAtPath(path: String) -> [String] { - if DebugOutputEnabled { - print("[debug] Finding script files at path: \(path)") - } - let items: [String] = { - do { - return try NSFileManager.defaultManager().contentsOfDirectoryAtPath(path) - } - catch { - return [] - } + return NSFileManager.defaultManager().subpathsAtPath(path) ?? [] }() return items - .filter() { $0 != ".apous.swift" && $0.pathExtension == "swift" } + .filter() { + let root = $0.pathComponents[0] + return $0.pathExtension == "swift" && (root != "Carthage" && root != "Rome" && root != "Pods") + } .map() { path.stringByAppendingPathComponent($0) } } - - diff --git a/src/main.swift b/src/main.swift index 8c9f77d..0b7942e 100644 --- a/src/main.swift +++ b/src/main.swift @@ -8,17 +8,8 @@ import Foundation -let CartfileConfig = "Cartfile" -let PodfileConfig = "Podfile" - -let ApousScriptFile = ".apous.swift" - -// TODO(owensd): Pull this from a proper versioning tool. -let Version = "0.1.1" -let Branch = "master" - func printUsage() { - print("OVERVIEW: Apous Swift Script Runner (build: \(Version)-\(Branch))") + print("OVERVIEW: Apous Swift Script Runner (build: \(VersionInfo.Version.rawValue)-\(VersionInfo.Branch.rawValue))") print("") print("USAGE: apous [|]") } @@ -41,53 +32,41 @@ let DebugOutputEnabled = arguments.contains("-debug") // NOTE(owensd): This method is a workaround because of Swift bugs and code in the top-level scope. func run() throws { let scriptItem = arguments[1.. $VERSION_FILE +echo "enum VersionInfo : String {" >> $VERSION_FILE +echo " case Version = \"$VERSION\"" >> $VERSION_FILE +echo " case Branch = \"$BRANCH_NAME\"" >> $VERSION_FILE +echo "}" >> $VERSION_FILE diff --git a/test/Info.plist b/test/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/test/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/test/Samples.swift b/test/Samples.swift new file mode 100644 index 0000000..1673b74 --- /dev/null +++ b/test/Samples.swift @@ -0,0 +1,54 @@ +// +// apoustest.swift +// apoustest +// +// Created by David Owens on 7/6/15. +// Copyright © 2015 owensd.io. All rights reserved. +// + +import XCTest + +class SamplesTest : XCTestCase { + + lazy var samplesPath: String = { + let path = NSBundle(forClass: self.dynamicType) + .bundlePath + .stringByDeletingLastPathComponent + .stringByAppendingPathComponent("samples") ?? "" + return path + }() + + func validateSampleToolOutput(sample: String, output: String) { + do { + let path: String = samplesPath.stringByAppendingPathComponent(sample) + if !NSFileManager.defaultManager().fileExistsAtPath(path) { + XCTFail("The given samples path does not exist: \(path)") + return + } + + let result = try tools.apous(path) + XCTAssertEqual( + result.out.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()), + output.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()), + "The sample output does not match the expected.") + } + catch { + XCTFail("An error occurred during test execution.") + } + } + + func testBasic() { + let output = "Hello! This is a simple sample that contains no dependencies." + validateSampleToolOutput("basic", output: output) + } + + func testMulti() { + let output = "foo: 2\nbar: 1" + validateSampleToolOutput("multi", output: output) + } + + func testNested() { + let output = "Testing Nested Directories\nabspath: abspath!\nbasename: basename!" + validateSampleToolOutput("nested", output: output) + } +}