diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..32d33ac0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +Sources/LinuxBridge/* linguist-vendored +Sources/OpenSSL/* linguist-vendored +Sources/cURL/* linguist-vendored +Xcode/PerfectLib/* linguist-vendored diff --git a/.gitignore b/.gitignore index 8615121b..9564a1a9 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ DerivedData *.hmap *.ipa *.xcuserstate +*.xcscmblueprint # CocoaPods # @@ -31,3 +32,27 @@ DerivedData # Carthage/Checkouts Carthage/Build + +*.d + +*.o + +*.swiftdeps + +*.swiftdoc + +*.swiftmodule + +*.so + +*.DS_Store + +# Perfect binaries +PerfectServer/perfectserverfcgi +PerfectServer/perfectserverhttp + +# SwiftPM +.build/ +Packages/ +PerfectLib.xcodeproj/ +docs/ diff --git a/.jazzy.yaml b/.jazzy.yaml new file mode 100644 index 00000000..330e7aed --- /dev/null +++ b/.jazzy.yaml @@ -0,0 +1,8 @@ +module: PerfectLib +author: PerfectlySoft +author_url: https://perfect.org +github_url: https://github.com/PerfectlySoft/Perfect +copyright: '© 2016 PerfectlySoft Inc. and the Perfect project authors' +theme: fullwidth +xcodebuild_arguments: [-target, PerfectLib, -toolchain, org.swift.3020160620a] +clean: true diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..12f8d364 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [mailto:info@perfect.org]. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CODE_OF_CONDUCT.zh_CN.md b/CODE_OF_CONDUCT.zh_CN.md new file mode 100644 index 00000000..0846bf3e --- /dev/null +++ b/CODE_OF_CONDUCT.zh_CN.md @@ -0,0 +1,53 @@ +# 贡献者行为规范 + +## 我们的承诺 + +为了实现一个开放、和谐的软件开发环境,作为该软件的贡献者和版本维护者,我们保证在社区参与项目的过 +程中,对所有人一视同仁。在社区中,任何人,无论年龄、身材、长相,或者身体是否有缺陷;无论种族、 +性别和学历出身;无论国籍、血统、宗教信仰和任何性取向,我们都承诺避免任何骚扰和歧视。 + +## 我们的标准 + +为创造良好的开发环境,我们鼓励: + +* 采用友好的语言,秉持包容的态度 +* 充分尊重不同的观点和对问题的不同看法 +* 耐心接纳建设性批评 +* 关注于为社区未来提高产品价值 +* 对其他社区成员保持同情心 + +无法接受的行为包括: + +* 淫秽言语或低级下流的措辞 +* 恶意评论,针对人身和政治观点的言语攻击 +* 公开或私下的言语骚扰 +* 未经许可公布他人隐私,包括实际住址或电子地址 +* 从专业角度考虑的其他不正当行为 + +## 我们的责任 + +项目管理维护人员有义务澄清本行为规范,并针对任何不当行为采取必要措施进行纠正。 + +项目管理维护人员有权并有义务删除、修改、抵制任何不符合本行为规范的评论、代码内容、文字提交和各类 +报告P,也有权因其各种如恐吓、侵犯或者伤害的不当行为暂时或永久屏蔽违规账号。 + +## 适用范围 + +本行为规范适用于任何本项目或本项目所在社区空间内的公众行为,如公众邮件账号、公众社交媒体账号, +或者用于在线和离线活动中的委托代表。项目管理维护人员可以根据需要澄清或委派项目代表。 + +## 加强措施 + +任何不当言论、骚扰和其他不可接受的不当行为都可以通过[mailto:info@perfect.org]进行举报。所有 +投诉均会被审理并根据需要进行调查。项目团队有义务为举报者身份保密。更多加强措施会在其他文件中单独 +公开。 + +对于不遵守本行为规范的项目管理人员,本项目其他领导成员可能会临时或永久取消其管理权。 + +## 文件归属 + +本文节选自 [Contributor Covenant][homepage], version 1.4, +可从以下网址查看: [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/Connectors/MongoDB/README.md b/Connectors/MongoDB/README.md deleted file mode 100644 index c1ae1d6a..00000000 --- a/Connectors/MongoDB/README.md +++ /dev/null @@ -1 +0,0 @@ -# Perfect - MongoDB Connector diff --git a/Connectors/MySQL/README.md b/Connectors/MySQL/README.md deleted file mode 100644 index c5b0cab2..00000000 --- a/Connectors/MySQL/README.md +++ /dev/null @@ -1 +0,0 @@ -# Perfect - MySQL Connector diff --git a/Connectors/PostgreSQL/README.md b/Connectors/PostgreSQL/README.md deleted file mode 100644 index fcfb6731..00000000 --- a/Connectors/PostgreSQL/README.md +++ /dev/null @@ -1 +0,0 @@ -# Perfect - PostgreSQL Connector diff --git a/LICENSE b/LICENSE index 8f71f43f..8dada3ed 100644 --- a/LICENSE +++ b/LICENSE @@ -199,4 +199,3 @@ 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. - diff --git a/LICENSE.zh_CN b/LICENSE.zh_CN new file mode 100644 index 00000000..3b8b5d58 --- /dev/null +++ b/LICENSE.zh_CN @@ -0,0 +1,59 @@ +Apache许可证 +2.0版 2004年1月 +http://www.apache.org/licenses/ + +关于使用、复制和分发的条款 + +定义 +"许可证"是指根据本文件第1到第9部分关于使用、复制和分发的条款。 + +"许可证颁发者"是指版权所有者或者由版权所有者授权许可证的实体。 + +"法律实体"是指实施实体和进行控制的所有其它实体受该实体控制,或者受该实体集中控制。根据此定义,"控制"是指(i)让无论是否签订协议的上述实体,进行指导或管理的直接权利或间接权利,或者(ii)拥有百分之五十(50%)或以上已发行股票的所有者,或者(iii)上述实体的实权所有者。 + +"用户"(或"用户的")是指行使本许可证所授予权限的个人或法律实体。 + +"源程序"形式是指对包括但不限于软件源代码、文件源程序和配置文件进行修改的首选形式。 + +"目标"形式是指对源程序形式进行机械转换或翻译的任何形式,包括但不限于对编译的目标代码,生成的文件以及转换为其它媒体类型。 + +"作品"是指根据本许可证所制作的源程序形式或目标形式的著作,在著作中包含的或附加的版权通知(在下面附录中提供了一个示例)。 + +"衍生作品"是指基于作品(或从作品衍生而来)的源程序形式或目标形式的任何作品,以及编辑修订、注释、详细描述或其它修订等构成原创著作作品的整体。根据本许可证,衍生作品不得包括与作品及其衍生作品分离之作品,或仅与作品及其衍生作品的接口相链接(或按名称结合)之作品。 + +"贡献"是指任何著作作品,包括作品的原始版本和对该作品或衍生作品所做的任何修订或补充,意在提交给许可证颁发者以让版权所有者或代表版权所有者的授权个人或法律实体包含在其作品中。根据此定义,"提交"一词表示发送给许可证颁发者或其代表人,任何电子的、口头的或书面的交流信息形式,包括但不限于在由许可证颁发者或者代表其管理的电子邮件清单、源代码控制系统、以及发布跟踪系统上为讨论和提高作品的交流,但不包括由版权所有者以书面形式明显标注或指定为"非贡献"的交流活动。 + +"贡献者"是指许可证颁发者和代表从许可证颁发者接受之贡献的并随后包含在作品之贡献中的任何个人或法律实体。 + +版权许可证的授予。根据本许可证的条款,每个贡献者授予用户永久性的、全球性的、非专有性的、免费的、无版权费的、不可撤销的版权许可证以源程序形式或目标形式复制、准备衍生作品、公开显示、公开执行、授予分许可证、以及分发作品和这样的衍生作品。 +专利许可证的授予。根据本许可证的条款,每个贡献者授予用户永久性的、全球性的、非专有性的、免费的、无版权费的、不可撤销的(除在本部分进行说明)专利许可证对作品进行制作、让人制作、使用、提供销售、销售、进口和其它转让,且这样的许可证仅适用于在所递交作品的贡献中因可由单一的或多个这样的贡献者授予而必须侵犯的申请专利。如果用户对任何实体针对作品或作品中所涉及贡献提出因直接性或贡献性专利侵权而提起专利法律诉讼(包括交互诉讼请求或反索赔),那么根据本许可证,授予用户针对作品的任何专利许可证将在提起上述诉讼之日起终止。 +重新分发。用户可在任何媒介中复制和分发作品或衍生作品之副本,无论是否修订,还是以源程序形式或目标形式,条件是用户需满足下列条款: +用户必须为作品或衍生作品的任何其他接收者提供本许可证的副本;并且 +用户必须让任何修改过的文件附带明显的通知,声明用户已更改文件;并且 +用户必须从作品的源程序形式中保留衍生作品源程序形式的用户所分发的所有版权、专利、商标和属性通知,但不包括不属于衍生作品任何部分的类似通知;并且 +如果作品将"通知"文本文件包括为其分发作品的一部分,那么用户分发的任何衍生作品中须至少在下列地方之一包括,在这样的通知文件中所包含的属性通知的可读副本,但不包括那些不属于衍生作品任何部分的通知:在作为衍生作品一部分而分发的通知文本文件中;如果与衍生作品一起提供则在源程序形式或文件中;或者通常作为第三方通知出现的时候和地方,在衍生作品中产生的画面中。通知文件的内容仅供信息提供,并未对许可证进行修改。用户可在其分发的衍生作品中在作品的通知文本后或作为附录添加自己的属性通知,条件是附加的属性通知不得构成修改本许可证。 +用户可以为自身所做出的修订添加自己的版权声明并可对自身所做出修订内容或为这样的衍生作品作为整体的使用、复制或分发提供附加或不同的条款,条件是用户对作品的使用、复制和分发必须符合本许可证中声明的条款。 + +贡献的提交。除非用户明确声明,在作品中由用户向许可证颁发者的提交若要包含在贡献中,必须在无任何附加条款下符合本许可证的条款。尽管上面如此规定,执行许可证颁发者有关贡献的条款时,任何情况下均不得替代或修改任何单独许可证协议的条款。 +商标。本许可证并未授予用户使用许可证颁发者的商号、商标、服务标记或产品名称,除非将这些名称用于合理性和惯例性描述作品起源和复制通知文件的内容时。 +保证否认条款。除非因适用法律需要或书面同意,许可证颁发者以"按原样"基础提供作品(并且每个贡献者提供其贡献),无任何明示的或暗示的保证或条件,包括但不限于关于所有权、不侵权、商品适销性、或适用性的保证或条件。用户仅对使用或重新分发作品的正确性负责,并需承担根据本许可证行使权限时的任何风险。 +责任限制条款。在任何情况下并根据任何法律,无论是因侵权(包括过失)或根据合同,还是其它原因,除非根据适用法律需要(例如故意行为和重大过失行为)或经书面同意,即使贡献者事先已被告知发生损害的可能性,任何贡献者不就用户因使用本许可证或不能使用或无法使用作品(包括但不限于商誉损失、停工、计算机失效或故障,或任何商业损坏或损失)而造成的损失,包括直接的、非直接的、特殊的、意外的或间接的字符损坏而负责。 +接受保证或附加责任。重新分发作品或及其衍生作品时,用户可选择提供或为符合本许可证承担之支持、担保、赔偿或其它职责义务和/或权利而收取费用。但是,在承担上述义务时,用户只可代表用户本身和用户本身责任来执行,无需代表任何其它贡献者,并且用户仅可保证、防护并保持每个贡献者不受任何因此而产生的责任或对因用户自身承担这样的保证或附加责任而对这样的贡献者所提出的索赔。 +条款结束 + +附录:如何向用户作品中应用Apache许可证。 + +若要向用户作品应用Apache许可证,请附加下列样本通知,将括号"[]"中的字段以用户自身的区分信息来替换(但不包括括号)。文本必须以文件格式适当的注释句法包含在其中。另外建议将文件名或类别名以及目的说明包含在相同的"打印页"上作为版权通知,以更加容易的区分出第三方档案。 + +版权所有[yyyy][版权所有者的名称] + +根据2.0版本Apache许可证("许可证")授权; +根据本许可证,用户可以不使用此文件。 +用户可从下列网址获得许可证副本: + +http://www.apache.org/licenses/LICENSE-2.0 + +除非因适用法律需要或书面同意, +根据许可证分发的软件是基于"按原样"基础提供, +无任何明示的或暗示的保证或条件。 +详见根据许可证许可下,特定语言的管辖权限和限制。 diff --git a/Package.swift b/Package.swift new file mode 100644 index 00000000..706bddee --- /dev/null +++ b/Package.swift @@ -0,0 +1,35 @@ +// +// Package.swift +// PerfectLib +// +// Created by Kyle Jessup on 3/22/16. +// Copyright (C) 2016 PerfectlySoft, Inc. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +import PackageDescription + +var urls = [String]() + +#if os(Linux) +urls += ["https://github.com/PerfectlySoft/Perfect-LinuxBridge.git"] +#else + +#endif + +let package = Package( + name: "PerfectLib", + targets: [], + dependencies: urls.map { .Package(url: $0, majorVersion: 2, minor: 0) }, + exclude: [] +) diff --git a/PerfectLib/README.md b/PerfectLib/README.md deleted file mode 100644 index 89ca3692..00000000 --- a/PerfectLib/README.md +++ /dev/null @@ -1 +0,0 @@ -# PerfectLib diff --git a/PerfectServer/README.md b/PerfectServer/README.md deleted file mode 100644 index fccf9d52..00000000 --- a/PerfectServer/README.md +++ /dev/null @@ -1 +0,0 @@ -# PerfectServer diff --git a/README.md b/README.md index 9cb76be7..9ae7116a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,149 @@ -# Perfect -================== +# Perfect: Server-Side Swift [简体中文](README.zh_CN.md) +

+ + Get Involed with Perfect! + +

+

+ + Star Perfect On Github + + + Stack Overflow + + + Follow Perfect on Twitter + + + Join the Perfect Slack + +

+

+ + Swift 3.0 + + + Platforms OS X | Linux + + + License Apache + + + codebeat + + + PerfectlySoft Twitter + + + Slack Status + +

+ +##Perfect: Server-Side Swift + +Perfect is a complete and powerful toolbox, framework, and application server for Linux, iOS, and macOS (OS X). It provides everything a Swift engineer needs for developing lightweight, maintainable, and scalable apps and other REST services entirely in the Swift programming language for both client-facing and server-side applications. + +Perfect includes a suite of tools that will enhance your productivity as you use only one programming language to build your apps: Swift. The global development community’s most dynamic and popular server-side toolbox and framework available today, Perfect is the backbone for many live web applications and apps available on iTunes. + +This guide is designed for developers at all levels of experience to get Perfect up and running quickly. + +## Working with Perfect + +###Compatibility with Swift + +The master branch of this project currently compiles with **Xcode 8.1** or the **Swift 3.0.1** toolchain on Ubuntu. + +###Getting Started + +[Access a tutorial](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/gettingStarted.md) to help you get started using Perfect quickly. It includes straightforward examples of how Perfect can be used. + +### Documentation +[Get started working with Perfect](https://github.com/PerfectlySoft/PerfectDocs), deploy your apps, and find more detailed help by consulting our reference library. + +We welcome contributions to Perfect’s documentation. If you spot a typo, bug, or other errata, or have additions or suggestions to recommend, please [create a pull request or log an issue in JIRA](http://jira.perfect.org:8080/servicedesk/customer/portal/1/user/login?destination=portal%2F1). + +### Community +We all need a little help now and then. If you do too, don’t be shy, ask us or the friendly and supportive Perfect community: + + +[Slack](http://perfect.ly/) | [Twitter](https://twitter.com/perfectlysoft) + +### Deployment + +Currently, there are options to deploy Perfect to [Docker](https://hub.docker.com/r/perfectlysoft/ubuntu/) and [Heroku](https://github.com/PerfectlySoft/Perfect-Heroku-Buildpack). We recommend you use these until the other deployment options are updated to compile with Swift 3.0 and Perfect 2.0. + +### Samples, Examples, and Tutorials + +Our library continues to grow as members of [the Swift-Perfect development community have shared many samples and examples](https://github.com/PerfectExamples) of their projects in Perfect. Examples include: + +- [WebSockets Server](https://github.com/PerfectExamples/PerfectExample-WebSocketsServer) +- [URL Routing](https://github.com/PerfectExamples/PerfectExample-URLRouting) +- [Upload Enumerator](https://github.com/PerfectExamples/PerfectExample-UploadEnumerator) + +There are [many more examples](https://github.com/PerfectExamples) you can explore. Please share yours! + +[Access tutorials for working in Perfect 1.0](http://perfect.org/tutorials.html) (which supports Swift 2.2) published by members of the Swift-Perfect community. Or [start working in Perfect 2.0](http://perfect.org/downloads.html#download-perfect) (supports Swift 3.0). + +## Core Perfect Modules + +Perfect project is divided into several repositories to make it easy for you to find, download, and install the components you need: + +- [Perfect](https://github.com/PerfectlySoft/Perfect) – This repository contains the core PerfectLib and will continue to be the main landing point for the project +- [Perfect Docs](https://github.com/PerfectlySoft/PerfectDocs) – Contains all API reference-related material + + +### Examples + +- [Perfect Template](https://github.com/PerfectlySoft/PerfectTemplate) - A simple starter project which compiles with the Swift Package Manager into a standalone executable HTTP server. This repository is ideal for starting a Perfect-based project +- [Perfect Examples](https://github.com/PerfectExamples) - All the Perfect example projects + + +### DataSources + + +- [Perfect Redis](https://github.com/PerfectlySoft/Perfect-Redis) - The Redis database connector +- [Perfect SQLite](https://github.com/PerfectlySoft/Perfect-SQLite) - SQLite3 database connector +- [Perfect PostgreSQL](https://github.com/PerfectlySoft/Perfect-PostgreSQL) - PostgreSQL database connector +- [Perfect MySQL](https://github.com/PerfectlySoft/Perfect-MySQL) - MySQL database connector +- [Perfect MongoDB](https://github.com/PerfectlySoft/Perfect-MongoDB) - MongoDB database connector +- [Perfect FileMaker](https://github.com/PerfectlySoft/Perfect-FileMaker) - FileMaker Server database connector + +### Utilities + +- [Perfect FastCGI Apache 2.4](https://github.com/PerfectlySoft/Perfect-FastCGI-Apache2.4) - Apache 2.4 FastCGI module; required for the Perfect FastCGI server variant +- [Perfect XML](https://github.com/PerfectlySoft/Perfect-XML) - DOM Core level 2 read-only APIs and XPath support +- [Perfect HTTP Server](https://github.com/PerfectlySoft/Perfect-HTTPServer) - HTTP 1.1 server for Perfect +- [Perfect Mustache](https://github.com/PerfectlySoft/Perfect-Mustache) - Mustache template support for Perfect +- [Perfect CURL](https://github.com/PerfectlySoft/Perfect-CURL) - cURL support for Perfect +- [Perfect WebSockets](https://github.com/PerfectlySoft/Perfect-WebSockets) - WebSockets support for Perfect +- [Perfect Zip](https://github.com/PerfectlySoft/Perfect-Zip) - provides simple zip and unzip functionality +- [Perfect Notifications](https://github.com/PerfectlySoft/Perfect-Notifications) - provides support for Apple Push Notification Service (APNS). + +## More about Perfect + +Perfect operates using either a standalone [HTTP server](https://github.com/PerfectlySoft/Perfect-HTTP), [HTTPS server](https://github.com/PerfectlySoft/Perfect-HTTPServer), or through [FastCGI server](https://github.com/PerfectlySoft/Perfect-FastCGI). It provides a system for loading your Swift-based modules at startup, for interfacing those modules with its request/response objects, or to the built-in [Mustache template processing system](https://github.com/PerfectlySoft/Perfect-Mustache). + +Perfect is built on a completely asynchronous, high-performance networking engine to provide a scalable option for internet services. It supports Secure Sockets Layer (SSL) encryption, and it features a suite of tools commonly required by internet servers such as [WebSockets](https://github.com/PerfectlySoft/Perfect-WebSockets) and [iOS push notifications](https://github.com/PerfectlySoft/Perfect-Notifications), but you are not limited to those options. + +Feel free to use your favourite JSON or templating systems, etc. + +### Join and Contribute to the Community + +The Swift-Perfect developer community is vital to improving Perfect and supporting one another.   + +You can help other developers by sharing your expertise and tips, as well as learn from others, by joining the [Perfect Slack channel](http://perfect.ly). Contributions of all kinds are welcome: reporting issues, updating documentation, fixing bugs, building examples, sharing projects, and any other tips that may help the Swift-Perfect community. + +If you would like to report an issue, make a new feature request, or help others by working on a known issue, please see the [Perfect JIRA repository](http://jira.perfect.org:8080/secure/Dashboard.jspa). + +If you would like to share your example project, tutorial, or video, please share the URL of your work on GitHub and [Twitter](https://twitter.com/perfectlysoft), and the Perfect team will highlight it to the community. + +### Code of Conduct +The Perfect team welcomes people of all ethnicities, nationalities, ages, gender, disability, levels of experience, and religious beliefs to use and contribute to the Perfect project. We pledge to foster and enforce a harassment-free environment of openness, respect, and cooperation for everyone in all project and public spaces online or offline. + +Please report any behaviour that violates our [Code of Conduct](https://github.com/PerfectlySoft/Perfect/blob/master/CODE_OF_CONDUCT.md) to [info@perfect.org](mailto:info@perfect.org). The Perfect team is committed to enforcing this Code of Conduct to ensure everyone who wishes to use, contribute to, and comment on the Perfect project may do so freely and openly and without fear of reprisal. + +We will investigate all complaints of unacceptable or abusive behaviour or comments expediently, and we will maintain the confidentiality of the person who reports any perceived infraction or wrongdoing to us. We will not tolerate any form of direct or indirect harassment or discrimination within the Swift-Perfect community, and will take appropriate, fair, and corrective action to any instance of inappropriate behaviour. + +The Perfect team maintains the right to remove, edit, or reject any comments, code, edits, or issues that do not align with our Code of Conduct. diff --git a/README.zh_CN.md b/README.zh_CN.md new file mode 100644 index 00000000..28b49ebd --- /dev/null +++ b/README.zh_CN.md @@ -0,0 +1,154 @@ +# Perfect:Swift 语言服务器端软件框架 [English](README.md) +

+ + Get Involed with Perfect! + +

+ +

+ + Star Perfect On Github + + + Stack Overflow + + + Follow Perfect on Twitter + + + Join the Perfect Slack + +

+ +

+ + Swift 3.0 + + + Platforms OS X | Linux + + + License Apache + + + codebeat + + + PerfectlySoft Twitter + + + Slack Status + +

+ +## Perfect:Swift 语言服务器端软件框架 + +Perfect是一组完整、强大的工具箱、软件框架体系和Web应用服务器,可以在Linux、iOS和macOS (OS X)上使用。该软件体系为Swift工程师量身定制了一整套用于开发轻量、易维护、规模可扩展的Web应用及其它REST服务的解决方案,这样Swift工程师就可以实现同时在服务器和客户端上采用同一种语言开发软件项目。 + +Perfect内建整套工具集,因为无论是客户端还是服务器都能够在此基础之上用同一种计算机语言Swift进行程序开发,因此能够为软件工程师大幅提高工作效率。在全球目前众多的服务器端框架体系和工具箱产品之中,Perfect目前已经成为许多iTunes在线应用程序的可靠后台应用。 + +无论您是资深程序员还是入门级的软件工程师,本文都能够帮助您快速启动Perfect实现服务器项目开发运行。 + +## 使用Perfect + +###Swift语言兼容性 + +**目前本项目主干版本基于Xcode 8 GM release发行版本。 ** + +``` +Current version: DEVELOPMENT-SNAPSHOT-2016-09-05-a, or Xcode 8 GM release +``` + + +###快速上手 + +[在线教程(简体中文)](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide.zh_CN/gettingStarted.md) 能够帮助您快速开始使用Perfect。该指南包括了如何使用Perfect的几个典型例子。 + +### 文档 +[Perfect帮助文档(简体中文)](https://github.com/PerfectlySoft/PerfectDocs) 如何部署应用程序、如何查找详细文档和帮助。 + +我们欢迎所有贡献以及对Perfect文档提高的宝贵意见。我们欢迎您为Perfect付出宝贵的支持。如果您发现了任何文字或者内容有错误,或者有任何建议,请[提交一个代码上传请求,或在JIRA上报告问题](http://jira.perfect.org:8080/servicedesk/customer/portal/1/user/login?destination=portal%2F1). + +### 社区 +我们总会需要您的帮助。如果您真的有想法,不妨加入我们的Perfect支持社区: + + +[Slack](http://perfect.ly/) | [Twitter](https://twitter.com/perfectlysoft) + +### 部署 + +目前,部署Perfect的方式可以选择[Docker](https://hub.docker.com/r/perfectlysoft/ubuntu/)和[Heroku](https://github.com/PerfectlySoft/Perfect-Heroku-Buildpack)。我们强烈推荐使用这种方式进行部署,因为这些部署方式是通过最新Swift 3.0 和 Perfect 2.0编译完成的。 + +### 教程和案例 + +我们的图书馆一直在随着社区成员的加入而不断增长,[Swift-Perfect开发社区有许多源程序共分享](https://github.com/PerfectlySoft/PerfectExamples),都是建立在Perfect程序框架之上。典型例子包括: + +- [WebSockets 服务器](https://github.com/PerfectlySoft/PerfectExample-WebSocketsServer) +- [URL 路由](https://github.com/PerfectlySoft/PerfectExample-URLRouting) +- [文件上传](https://github.com/PerfectlySoft/PerfectExample-UploadEnumerator) + +[更多例子敬请关注!](https://github.com/PerfectlySoft/PerfectExamples) + +[Perfect 1.0教程](http://perfect.org/tutorials.html) (支持 Swift 2.2) 由Swift-Perfect社区成员贡献。或者[从Perfect 2.0开始](http://perfect.org/downloads.html#download-perfect) (支持 Swift 3.0). + +## 核心 Perfect 模块 + +Perfect 项目由若干代码资源库构成,便于您按需查找、下载和安装必要的组件: + +- [Perfect](https://github.com/PerfectlySoft/Perfect):核心的程序库和基础软件框架 +- [Perfect Docs](https://github.com/PerfectlySoft/PerfectDocs):所有必要的程序文档和帮助内容 + + +### 参考和样例 + +- [Perfect 模板](https://github.com/PerfectlySoft/PerfectTemplate):一个使用SPM软件包管理器快速上手的入门项目,能够编译为一个独立运行的HTTP服务器。该代码资源非常适合基于Perfect的项目就此开始开发过程。 +- [Perfect 样例](https://github.com/PerfectlySoft/PerfectExamples):所有Perfect 项目的典型样例 + + +### 数据源 + + +- [Perfect Redis](https://github.com/PerfectlySoft/Perfect-Redis):Redis 数据库连接工具 +- [Perfect SQLite](https://github.com/PerfectlySoft/Perfect-SQLite):SQLite3 数据库连接工具 +- [Perfect PostgreSQL](https://github.com/PerfectlySoft/Perfect-PostgreSQL):PostgreSQL 数据库连接工具 +- [Perfect MySQL](https://github.com/PerfectlySoft/Perfect-MySQL):MySQL 数据库连接工具 +- [Perfect MongoDB](https://github.com/PerfectlySoft/Perfect-MongoDB):MongoDB 数据库连接工具 +- [Perfect FileMaker](https://github.com/PerfectlySoft/Perfect-FileMaker):FileMaker 数据库连接工具 + +### 工具集 + +- [Perfect FastCGI Apache 2.4](https://github.com/PerfectlySoft/Perfect-FastCGI-Apache2.4) - Apache 2.4 FastCGI 模块。如果您使用FastCGI用于基础Web服务,请使用该模块 +- [Perfect XML](https://github.com/PerfectlySoft/Perfect-XML) - DOM文档对象二级核心只读函数库和XPath路径支持 +- [Perfect HTTP Server](https://github.com/PerfectlySoft/Perfect-HTTPServer) - HTTP 1.1标准的 Perfect服务器 +- [Perfect Mustache](https://github.com/PerfectlySoft/Perfect-Mustache) - Mustache静态模板支持 +- [Perfect CURL](https://github.com/PerfectlySoft/Perfect-CURL) - cURL网页传输支持 +- [Perfect WebSockets](https://github.com/PerfectlySoft/Perfect-WebSockets) - 网络套接字WebSockets支持 +- [Perfect Zip](https://github.com/PerfectlySoft/Perfect-Zip) - 提供简单的zip压缩和解压缩功能 +- [Perfect Notifications](https://github.com/PerfectlySoft/Perfect-Notifications) - 提供苹果消息推送服务支持(APNS) + +## 更多内容 + +Perfect 可以作为一个独立的[HTTP服务器](https://github.com/PerfectlySoft/Perfect-HTTP)或[HTTPS加密服务器](https://github.com/PerfectlySoft/Perfect-HTTPServer)进行运行,或者通过[FastCGI快速网关服务器](https://github.com/PerfectlySoft/Perfect-FastCGI)进行运行。简单来说就是提供一个能够在系统启动是加载的Web服务,从而能够将您自行开发的Swift源码模块根据URL路由要求实现请求/响应,或者根据内建的[Mustache模板](https://github.com/PerfectlySoft/Perfect-Mustache)处理页面。 + +Perfect是一个完全异步、高性能的网络引擎,并且能够为互联网服务提供大吞吐量控制。该软件体系支持安全套接字(SSL)加密,并且封装了一系列互联网服务器通用的特性,比如[WebSockets](https://github.com/PerfectlySoft/Perfect-WebSockets) 和 [iOS消息推送](https://github.com/PerfectlySoft/Perfect-Notifications)。然而,您的开发可以不必受限于这些选项。 + +请根据您自己的喜好使用JSON或者其他的模板系统,等等。 + +### 加入我们的开发社区并贡献自己的力量 + +Swift-Perfect开发者社区是改进Perfect产品并实现客户支持的关键。 + +在社区里,您可以通过加入[Perfect Slack 频道](http://perfect.ly)和[Perfect Gitter 频道](https://gitter.im/PerfectlySoft/Perfect)互相帮助、分享技术、互相学习和研究诀窍。任何一种贡献方式我们都非常欢迎:问题汇报、文档更新、补丁修复、编写案例、分享项目或者任何编程窍门,我们相信这些都能够极大地帮助我们的Swift-Perfect社区。 + +如果您发现了任何文字或者内容有错误,或者有任何建议,请[查看我们的Perfect JIRA资源库](http://jira.perfect.org:8080/secure/Dashboard.jspa). + +如果您希望分享一下您的项目、教程或者视频,请将URL共享到我们的推特或者GitHub账号:[Perfect 推特](https://twitter.com/perfectlysoft)。之后我们的Perfect团队会继续推广。 + +### 行为规范 +Perfect团队欢迎所有不同种族、国际、不同年龄、性别、身残志坚、不同学历出身、不同宗教信仰的人为我们的Perfect项目作出贡献。我们承诺为所有项目和公众在线/离线空间提供一个开放、祥和、互相尊重、共同工作的环境。 + +如果您发现有任何违反上述[行为规范](https://github.com/PerfectlySoft/Perfect/blob/master/CODE_OF_CONDUCT.zh_CN.md)的行为,请[给我们写邮件](mailto:info@perfect.org)。Perfect团队承诺致力于维护上述价值观念以确保所有参与者和用户都能实现对Perfect项目的充分开放的自由使用、自由评论和自由贡献,不需要对任何恐吓而害怕和妥协。 + +我们会调查任何不当行为与不当言论的投诉,同时我们会对检举人身份保密,便于对各种违法行为进行举报。我们不会容忍在Swift-Perfect社区内的任何直接或间接的骚扰或歧视,并会针对各类不当行为采取适度、公平的纠正措施。 + +Perfect团队有权删除、修改或拒绝任何不符合我们行为规范的各种言论、代码、版本或问题报告。 diff --git a/Sources/PerfectLib/Bytes.swift b/Sources/PerfectLib/Bytes.swift new file mode 100644 index 00000000..7fcf1297 --- /dev/null +++ b/Sources/PerfectLib/Bytes.swift @@ -0,0 +1,198 @@ +// +// Bytes.swift +// PerfectLib +// +// Created by Kyle Jessup on 7/7/15. +// Copyright (C) 2015 PerfectlySoft, Inc. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +/// A Bytes object represents an array of UInt8 and provides various utilities for importing and exporting values into and out of that array. +/// The object maintains a position marker which is used to denote the position from which new export operations proceed. +/// An export will advance the position by the appropriate amount. +public final class Bytes { + + /// The position from which new export operations begin. + public var position = 0 + /// The underlying UInt8 array + public var data: [UInt8] + + /// Indicates the number of bytes which may be successfully exported + public var availableExportBytes: Int { return self.data.count - self.position } + + /// Create an empty Bytes object + public init() { + self.data = [UInt8]() + } + + /// Initialize with existing bytes + public init(existingBytes: [UInt8]) { + self.data = existingBytes + } + + // -- IMPORT + /// Imports one UInt8 value appending it to the end of the array + /// - returns: The Bytes object + @discardableResult + public func import8Bits(from frm: UInt8) -> Bytes { + data.append(frm) + return self + } + + /// Imports one UInt16 value appending it to the end of the array + /// - returns: The Bytes object + @discardableResult + public func import16Bits(from frm: UInt16) -> Bytes { + data.append(UInt8(frm & 0xFF)) + data.append(UInt8((frm >> 8) & 0xFF)) + return self + } + + /// Imports one UInt32 value appending it to the end of the array + /// - returns: The Bytes object + @discardableResult + public func import32Bits(from frm: UInt32) -> Bytes { + data.append(UInt8(frm & 0xFF)) + data.append(UInt8((frm >> 8) & 0xFF)) + data.append(UInt8((frm >> 16) & 0xFF)) + data.append(UInt8((frm >> 24) & 0xFF)) + return self + } + + /// Imports one UInt64 value appending it to the end of the array + /// - returns: The Bytes object + @discardableResult + public func import64Bits(from frm: UInt64) -> Bytes { + data.append(UInt8(frm & 0xFF)) + data.append(UInt8((frm >> 8) & 0xFF)) + data.append(UInt8((frm >> 16) & 0xFF)) + data.append(UInt8((frm >> 24) & 0xFF)) + data.append(UInt8((frm >> 32) & 0xFF)) + data.append(UInt8((frm >> 40) & 0xFF)) + data.append(UInt8((frm >> 48) & 0xFF)) + data.append(UInt8((frm >> 56) & 0xFF)) + return self + } + + /// Imports an array of UInt8 values appending them to the end of the array + /// - returns: The Bytes object + @discardableResult + public func importBytes(from frm: [UInt8]) -> Bytes { + data.append(contentsOf: frm) + return self + } + + /// Imports the array values of the given Bytes appending them to the end of the array + /// - returns: The Bytes object + @discardableResult + public func importBytes(from frm: Bytes) -> Bytes { + data.append(contentsOf: frm.data) + return self + } + + /// Imports an `ArraySlice` of UInt8 values appending them to the end of the array + /// - returns: The Bytes object + @discardableResult + public func importBytes(from frm: ArraySlice) -> Bytes { + data.append(contentsOf: frm) + return self + } + + // -- EXPORT + + /// Exports one UInt8 from the current position. Advances the position marker by 1 byte. + /// - returns: The UInt8 value + public func export8Bits() -> UInt8 { + defer { + position += 1 + } + return data[position] + } + + /// Exports one UInt16 from the current position. Advances the position marker by 2 bytes. + /// - returns: The UInt16 value + public func export16Bits() -> UInt16 { + + let one = UInt16(data[position]) + position += 1 + let two = UInt16(data[position]) + position += 1 + + return (two << 8) + one + } + + /// Exports one UInt32 from the current position. Advances the position marker by 4 bytes. + /// - returns: The UInt32 value + public func export32Bits() -> UInt32 { + let one = UInt32(data[position]) + position += 1 + let two = UInt32(data[position]) + position += 1 + let three = UInt32(data[position]) + position += 1 + let four = UInt32(data[position]) + position += 1 + + return (four << 24) + (three << 16) + (two << 8) + one + } + + /// Exports one UInt64 from the current position. Advances the position marker by 8 bytes. + /// - returns: The UInt64 value + public func export64Bits() -> UInt64 { + let one = UInt64(data[position]) + position += 1 + let two = UInt64(data[position]) << 8 + position += 1 + let three = UInt64(data[position]) << 16 + position += 1 + let four = UInt64(data[position]) << 24 + position += 1 + let five = UInt64(data[position]) << 32 + position += 1 + let six = UInt64(data[position]) << 40 + position += 1 + let seven = UInt64(data[position]) << 48 + position += 1 + let eight = UInt64(data[position]) << 56 + position += 1 + + return (one+two+three+four)+(five+six+seven+eight) + } + + /// Exports the indicated number of bytes + public func exportBytes(count cnt: Int) -> [UInt8] { + var sub = [UInt8]() + let end = self.position + cnt + while self.position < end { + sub.append(self.data[self.position]) + self.position += 1 + } + return sub + } +} + + + + + + + + + + + + + + + + diff --git a/Sources/PerfectLib/Dir.swift b/Sources/PerfectLib/Dir.swift new file mode 100644 index 00000000..8fa9e05d --- /dev/null +++ b/Sources/PerfectLib/Dir.swift @@ -0,0 +1,184 @@ +// +// Dir.swift +// PerfectLib +// +// Created by Kyle Jessup on 7/7/15. +// Copyright (C) 2015 PerfectlySoft, Inc. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +import Foundation + +#if os(Linux) +import LinuxBridge +#else +import Darwin +#endif + +/// This class represents a directory on the file system. +/// It can be used for creating & inspecting directories and enumerating directory contents. +public struct Dir { + /// A typealias for directory permission modes. + public typealias PermissionMode = File.PermissionMode + + var internalPath = "" + + /// Create a new Dir object with the given path + public init(_ path: String) { + let pth = path.ends(with: "/") ? path : path + "/" + self.internalPath = File.resolveTilde(inPath: pth) + } + + /// Returns true if the directory exists + public var exists: Bool { + return exists(realPath) + } + + /// Set this Dir as the process' working directory. + public func setAsWorkingDir() throws { + let res = chdir(self.internalPath) + guard res == 0 else { + try ThrowFileError() + } + } + + /// Return the process' current working directory. + public static var workingDir: Dir { + let buffer = Array(repeating: 0 as UInt8, count: 2049) + let _ = getcwd(UnsafeMutableRawPointer(mutating: buffer).assumingMemoryBound(to: Int8.self), 2048) + let path = String(validatingUTF8: UnsafeMutableRawPointer(mutating: buffer).assumingMemoryBound(to: Int8.self)) ?? "." + return Dir(path) + } + + func exists(_ path: String) -> Bool { + return access(path, F_OK) != -1 + } + + /// Creates the directory using the provided permissions. All directories along the path will be created if need be. + /// - parameter perms: The permissions for use for the new directory and preceeding directories which need to be created. Defaults to RWX-GUO + /// - throws: `PerfectError.FileError` + public func create(perms: PermissionMode = [.rwxUser, .rxGroup, .rxOther]) throws { + let pth = realPath + var currPath = pth.begins(with: "/") ? "/" : "" + for component in pth.filePathComponents where component != "/" { + currPath += component + defer { + currPath += "/" + } + guard !exists(currPath) else { + continue + } + let res = mkdir(currPath, perms.rawValue) + guard res != -1 else { + try ThrowFileError() + } + } + } + + /// Deletes the directory. The directory must be empty in order to be successfuly deleted. + /// - throws: `PerfectError.FileError` + public func delete() throws { + let res = rmdir(realPath) + guard res != -1 else { + try ThrowFileError() + } + } + + /// Returns the name of the directory. + public var name: String { + return internalPath.lastFilePathComponent + } + + /// Returns a Dir object representing the current Dir's parent. Returns nil if there is no parent. + public var parentDir: Dir? { + guard internalPath != "/" else { + return nil // can not go up + } + return Dir(internalPath.deletingLastFilePathComponent) + } + + /// Returns the path to the current directory. + public var path: String { + return internalPath + } + + /// Returns the UNIX style permissions for the directory. + public var perms: PermissionMode { + get { + return File(internalPath).perms + } + set { + File(internalPath).perms = newValue + } + } + + var realPath: String { + return internalPath.resolvingSymlinksInFilePath + } + +#if os(Linux) + func readDir(_ d: OpaquePointer, _ dirEnt: inout dirent, _ endPtr: UnsafeMutablePointer?>!) -> Int32 { + return readdir_r(d, &dirEnt, endPtr) + } +#else + func readDir(_ d: UnsafeMutablePointer, _ dirEnt: inout dirent, _ endPtr: UnsafeMutablePointer?>!) -> Int32 { + return readdir_r(d, &dirEnt, endPtr) + } +#endif + + /// Enumerates the contents of the directory passing the name of each contained element to the provided callback. + /// - parameter closure: The callback which will receive each entry's name + /// - throws: `PerfectError.FileError` + public func forEachEntry(closure: (String) throws -> ()) throws { + guard let dir = opendir(realPath) else { + try ThrowFileError() + } + + defer { closedir(dir) } + + var ent = dirent() + let entPtr = UnsafeMutablePointer?>.allocate(capacity: 1) + defer { entPtr.deallocate(capacity: 1) } + + while readDir(dir, &ent, entPtr) == 0 && entPtr.pointee != nil { + let name = ent.d_name + #if os(Linux) + let nameLen = 1024 + #else + let nameLen = ent.d_namlen + #endif + let type = ent.d_type + + var nameBuf = [CChar]() + let mirror = Mirror(reflecting: name) + let childGen = mirror.children.makeIterator() + for _ in 0..> 3) +let S_IWGRP = (S_IWUSR >> 3) +let S_IXGRP = (S_IXUSR >> 3) +let S_IRWXU = (__S_IREAD|__S_IWRITE|__S_IEXEC) +let S_IRWXG = (S_IRWXU >> 3) +let S_IRWXO = (S_IRWXG >> 3) +let S_IROTH = (S_IRGRP >> 3) +let S_IWOTH = (S_IWGRP >> 3) +let S_IXOTH = (S_IXGRP >> 3) + +let SEEK_CUR: Int32 = 1 +let EXDEV = Int32(18) +let EACCES = Int32(13) +let EAGAIN = Int32(11) +let F_OK: Int32 = 0 + +#else +import Darwin +#endif + +extension stat { + var sec: Int { + #if os(Linux) + return Int(st_mtim.tv_sec) + #else + return Int(st_mtimespec.tv_sec) + #endif + } +} + +let fileCopyBufferSize = 16384 + +/// Provides access to a file on the local file system +public class File { + + /// The underlying file system descriptor. + public var fd = -1 + var internalPath = "" + + /// Checks that the file exists on the file system + /// - returns: True if the file exists or false otherwise + public var exists: Bool { + return access(internalPath, F_OK) != -1 + } + + /// Returns true if the file has been opened + public var isOpen: Bool { + return fd != -1 + } + + /// Returns the file's path + public var path: String { return internalPath } + + /// Returns the file path. If the file is a symbolic link, the link will be resolved. + public var realPath: String { + let maxPath = 2048 + guard isLink else { + return internalPath + } + var ary = [UInt8](repeating: 0, count: maxPath) + let buffer = UnsafeMutableRawPointer(mutating: ary).assumingMemoryBound(to: Int8.self) + let res = readlink(internalPath, buffer, maxPath) + guard res != -1 else { + return internalPath + } + ary.removeLast(maxPath - res) + let trailPath = UTF8Encoding.encode(bytes: ary) + let lastChar = trailPath[trailPath.startIndex] + guard lastChar != "/" && lastChar != "." else { + return trailPath + } + return internalPath.deletingLastFilePathComponent + "/" + trailPath + } + + /// Returns the modification date for the file in the standard UNIX format of seconds since 1970/01/01 00:00:00 GMT + /// - returns: The date as Int + public var modificationTime: Int { + var st = stat() + let res = isOpen ? fstat(Int32(fd), &st) : stat(internalPath, &st) + guard res == 0 else { + return Int.max + } + + return st.sec + } + + static func resolveTilde(inPath: String) -> String { + if !inPath.isEmpty && inPath[inPath.startIndex] == "~" { + var wexp = wordexp_t() + guard 0 == wordexp(inPath, &wexp, 0), + let we_wordv = wexp.we_wordv else { + return inPath + } + defer { + wordfree(&wexp) + } + if let resolved = we_wordv[0], let pth = String(validatingUTF8: resolved) { + return pth + } + } + return inPath + } + + /// Create a file object given a path and open mode + /// - parameter path: Path to the file which will be accessed + /// - parameter fd: The file descriptor, if any, for an already opened file + public init(_ path: String, fd: Int32 = -1) { + self.internalPath = File.resolveTilde(inPath: path) + self.fd = Int(fd) + } + + /// Closes the file if it had been opened + public func close() { + if fd != -1 { + #if os(Linux) + _ = SwiftGlibc.close(CInt(fd)) + #else + _ = Darwin.close(CInt(fd)) + #endif + fd = -1 + } + } + + /// Resets the internal file descriptor, leaving the file opened if it had been. + public func abandon() { + fd = -1 + } +} + +public extension File { + /// The open mode for the file. + public enum OpenMode { + /// Opens the file for read-only access. + case read + /// Opens the file for write-only access, creating the file if it did not exist. + case write + /// Opens the file for read-write access, creating the file if it did not exist. + case readWrite + /// Opens the file for read-write access, creating the file if it did not exist and moving the file marker to the end. + case append + /// Opens the file for read-write access, creating the file if it did not exist and setting the file's size to zero. + case truncate + + var toMode: Int { + switch self { + case .read: return Int(O_RDONLY) + case .write: return Int(O_WRONLY|O_CREAT) + case .readWrite: return Int(O_RDWR|O_CREAT) + case .append: return Int(O_RDWR|O_APPEND|O_CREAT) + case .truncate: return Int(O_RDWR|O_TRUNC|O_CREAT) + } + } + } + /// A file or directory access permission value. + public struct PermissionMode: OptionSet { + /// File system mode type. + public typealias Mode = mode_t + /// The raw mode. + public let rawValue: Mode + /// Create a permission mode with a raw value. + public init(rawValue: Mode) { + self.rawValue = rawValue + } + +#if os(Linux) + init(rawValue: Int32) { + self.init(rawValue: UInt32(rawValue)) + } +#endif + + /// Readable by user. + public static let readUser = PermissionMode(rawValue: S_IRUSR) + /// Writable by user. + public static let writeUser = PermissionMode(rawValue: S_IWUSR) + /// Executable by user. + public static let executeUser = PermissionMode(rawValue: S_IXUSR) + /// Readable by group. + public static let readGroup = PermissionMode(rawValue: S_IRGRP) + /// Writable by group. + public static let writeGroup = PermissionMode(rawValue: S_IWGRP) + /// Executable by group. + public static let executeGroup = PermissionMode(rawValue: S_IXGRP) + /// Readable by others. + public static let readOther = PermissionMode(rawValue: S_IROTH) + /// Writable by others. + public static let writeOther = PermissionMode(rawValue: S_IWOTH) + /// Executable by others. + public static let executeOther = PermissionMode(rawValue: S_IXOTH) + + /// Read, write, execute by user. + public static let rwxUser: PermissionMode = [.readUser, .writeUser, .executeUser] + /// Read, write by user and group. + public static let rwUserGroup: PermissionMode = [.readUser, .writeUser, .readGroup, .writeGroup] + + /// Read, execute by group. + public static let rxGroup: PermissionMode = [.readGroup, .executeGroup] + /// Read, execute by other. + public static let rxOther: PermissionMode = [.readOther, .executeOther] + + } + + /// Opens the file using the given mode. + /// - throws: `PerfectError.FileError` + public func open(_ mode: OpenMode = .read, permissions: PermissionMode = .rwUserGroup) throws { + if fd != -1 { + close() + } + #if os(Linux) + let openFd = linux_open(internalPath, CInt(mode.toMode), permissions.rawValue) + #else + let openFd = Darwin.open(internalPath, CInt(mode.toMode), permissions.rawValue) + #endif + guard openFd != -1 else { + try ThrowFileError() + } + fd = Int(openFd) + } +} + +public extension File { + /// The current file read/write position. + public var marker: Int { + /// Returns the value of the file's current position marker + get { + if isOpen { + return Int(lseek(Int32(self.fd), 0, SEEK_CUR)) + } + return 0 + } + /// Sets the file's position marker given the value as measured from the begining of the file. + set { + lseek(Int32(self.fd), off_t(newValue), SEEK_SET) + } + } +} + +public extension File { + + /// Closes and deletes the file + public func delete() { + close() + unlink(path) + } + + /// Moves the file to the new location, optionally overwriting any existing file + /// - parameter path: The path to move the file to + /// - parameter overWrite: Indicates that any existing file at the destination path should first be deleted + /// - returns: Returns a new file object representing the new location + /// - throws: `PerfectError.FileError` + public func moveTo(path: String, overWrite: Bool = false) throws -> File { + let destFile = File(path) + if destFile.exists { + guard overWrite else { + throw PerfectError.fileError(-1, "Can not overwrite existing file") + } + destFile.delete() + } + close() + let res = rename(self.path, path) + if res == 0 { + return destFile + } + if errno == EXDEV { + _ = try self.copyTo(path: path, overWrite: overWrite) + self.delete() + return destFile + } + try ThrowFileError() + } + + /// Copies the file to the new location, optionally overwriting any existing file + /// - parameter path: The path to copy the file to + /// - parameter overWrite: Indicates that any existing file at the destination path should first be deleted + /// - returns: Returns a new file object representing the new location + /// - throws: `PerfectError.FileError` + @discardableResult + public func copyTo(path pth: String, overWrite: Bool = false) throws -> File { + let destFile = File(pth) + if destFile.exists { + guard overWrite else { + throw PerfectError.fileError(-1, "Can not overwrite existing file") + } + destFile.delete() + } + let wasOpen = self.isOpen + let oldMarker = self.marker + if !wasOpen { + try open() + } else { + _ = marker = 0 + } + defer { + if !wasOpen { + close() + } else { + _ = marker = oldMarker + } + } + + try destFile.open(.truncate) + + var bytes = try self.readSomeBytes(count: fileCopyBufferSize) + while bytes.count > 0 { + try destFile.write(bytes: bytes) + bytes = try self.readSomeBytes(count: fileCopyBufferSize) + } + + destFile.close() + return destFile + } +} + +public extension File { + + /// Returns the size of the file in bytes + public var size: Int { + var st = stat() + let statRes = isOpen ? fstat(Int32(fd), &st) : stat(internalPath, &st) + guard statRes != -1 else { + return 0 + } + return Int(st.st_size) + } + + /// Returns true if the file is actually a directory + public var isDir: Bool { + var st = stat() + let statRes = isOpen ? fstat(Int32(fd), &st) : stat(internalPath, &st) + guard statRes != -1 else { + return false + } + let mode = st.st_mode + return (Int32(mode) & Int32(S_IFMT)) == Int32(S_IFDIR) + } + + /// Returns the UNIX style permissions for the file + public var perms: PermissionMode { + get { + var st = stat() + let statRes = isOpen ? fstat(Int32(fd), &st) : stat(internalPath, &st) + guard statRes != -1 else { + return PermissionMode(rawValue: PermissionMode.Mode(0)) + } + let mode = st.st_mode + return PermissionMode(rawValue: mode_t(Int32(mode) ^ Int32(S_IFMT))) + } + set { + _ = chmod(internalPath, newValue.rawValue) + } + } +} + +public extension File { + /// Returns true if the file is a symbolic link + public var isLink: Bool { + var st = stat() + let statRes = lstat(internalPath, &st) + guard statRes != -1 else { + return false + } + let mode = st.st_mode + return (Int32(mode) & Int32(S_IFMT)) == Int32(S_IFLNK) + } + + @discardableResult + public func linkTo(path: String, overWrite: Bool = false) throws -> File { + let destFile = File(path) + if destFile.exists { + guard overWrite else { + throw PerfectError.fileError(-1, "Can not overwrite existing file") + } + destFile.delete() + } + let res = symlink(self.path, path) + if res == 0 { + return File(path) + } + try ThrowFileError() + } +} + +public extension File { + + /// Reads up to the indicated number of bytes from the file + /// - parameter count: The maximum number of bytes to read + /// - returns: The bytes read as an array of UInt8. May have a count of zero. + /// - throws: `PerfectError.FileError` + public func readSomeBytes(count: Int) throws -> [UInt8] { + if !isOpen { + try open() + } + + func sizeOr(_ value: Int) -> Int { + var st = stat() + let statRes = isOpen ? fstat(Int32(fd), &st) : stat(internalPath, &st) + guard statRes != -1 else { + return 0 + } + if (Int32(st.st_mode) & Int32(S_IFMT)) == Int32(S_IFREG) { + return Int(st.st_size) + } + return value + } + + let bSize = min(count, sizeOr(count)) + var ary = [UInt8](repeating: 0, count: bSize) + let ptr = UnsafeMutableRawPointer(mutating: ary).assumingMemoryBound(to: Int8.self) + + let readCount = read(CInt(fd), ptr, bSize) + guard readCount >= 0 else { + try ThrowFileError() + } + if readCount < bSize { + ary.removeLast(bSize - readCount) + } + return ary + } + + /// Reads the entire file as a string + public func readString() throws -> String { + let bytes = try self.readSomeBytes(count: self.size) + return UTF8Encoding.encode(bytes: bytes) + } +} + +public extension File { + + /// Writes the string to the file using UTF-8 encoding + /// - parameter s: The string to write + /// - returns: Returns the number of bytes which were written + /// - throws: `PerfectError.FileError` + @discardableResult + public func write(string: String) throws -> Int { + return try write(bytes: Array(string.utf8)) + } + + /// Write the indicated bytes to the file + /// - parameter bytes: The array of UInt8 to write. + /// - parameter dataPosition: The offset within `bytes` at which to begin writing. + /// - parameter length: The number of bytes to write. + /// - throws: `PerfectError.FileError` + @discardableResult + public func write(bytes: [UInt8], dataPosition: Int = 0, length: Int = Int.max) throws -> Int { + let len = min(bytes.count - dataPosition, length) + let ptr = UnsafeMutableRawPointer(mutating: bytes).assumingMemoryBound(to: UInt8.self).advanced(by: dataPosition) + #if os(Linux) + let wrote = SwiftGlibc.write(Int32(fd), ptr, len) + #else + let wrote = Darwin.write(Int32(fd), ptr, len) + #endif + guard wrote == len else { + try ThrowFileError() + } + return wrote + } +} + +public extension File { + + /// Attempts to place an advisory lock starting from the current position marker up to the indicated byte count. This function will block the current thread until the lock can be performed. + /// - parameter byteCount: The number of bytes to lock + /// - throws: `PerfectError.FileError` + public func lock(byteCount: Int) throws { + if !isOpen { + try open(.write) + } + let res = lockf(Int32(self.fd), F_LOCK, off_t(byteCount)) + guard res == 0 else { + try ThrowFileError() + } + } + + /// Unlocks the number of bytes starting from the current position marker up to the indicated byte count. + /// - parameter byteCount: The number of bytes to unlock + /// - throws: `PerfectError.FileError` + public func unlock(byteCount: Int) throws { + if !isOpen { + try open(.write) + } + let res = lockf(Int32(self.fd), F_ULOCK, off_t(byteCount)) + guard res == 0 else { + try ThrowFileError() + } + } + + /// Attempts to place an advisory lock starting from the current position marker up to the indicated byte count. This function will throw an exception if the file is already locked, but will not block the current thread. + /// - parameter byteCount: The number of bytes to lock + /// - throws: `PerfectError.FileError` + public func tryLock(byteCount: Int) throws { + if !isOpen { + try open(.write) + } + let res = lockf(Int32(self.fd), F_TLOCK, off_t(byteCount)) + guard res == 0 else { + try ThrowFileError() + } + } + + /// Tests if the indicated bytes are locked + /// - parameter byteCount: The number of bytes to test + /// - returns: True if the file is locked + /// - throws: `PerfectError.FileError` + public func testLock(byteCount: Int) throws -> Bool { + if !isOpen { + try open(.write) + } + let res = Int(lockf(Int32(self.fd), F_TEST, off_t(byteCount))) + guard res == 0 || res == Int(EACCES) || res == Int(EAGAIN) else { + try ThrowFileError() + } + return res != 0 + } +} + +// Subclass to represent a file which can not be closed +private final class UnclosableFile : File { + override init(_ path: String, fd: Int32) { + super.init(path, fd: fd) + } + override func close() { + // nothing + } +} + +/// A temporary, randomly named file. +public final class TemporaryFile: File { + /// Create a temporary file, usually in the system's /tmp/ directory + /// - parameter withPrefix: The prefix for the temporary file's name. Random characters will be appended to the file's eventual name. + public convenience init(withPrefix: String) { + let template = withPrefix + "XXXXXX" + let utf8 = template.utf8 + let name = UnsafeMutablePointer.allocate(capacity: utf8.count + 1) + var i = utf8.startIndex + for index in 0.. JSONConvertibleObject + + static private var jsonDecodableRegistry = [String:JSONConvertibleObjectCreator]() + + /// Register a custom object to be JSON encoded/decoded. + static public func registerJSONDecodable(name nam: String, creator: @escaping JSONConvertibleObjectCreator) { + JSONDecoding.jsonDecodableRegistry[nam] = creator + } + + /// Instantiate a custom object based on the JSON data. + /// The system will use the `JSONDecoding.objectIdentifierKey` key to locate the custom object creator. + static public func createJSONConvertibleObject(values:[String:Any]) -> JSONConvertibleObject? { + guard let objkey = values[JSONDecoding.objectIdentifierKey] as? String else { + return nil + } + return JSONDecoding.createJSONConvertibleObject(name: objkey, values: values) + } + + /// Instantiate a custom object based on the JSON data. + /// The system will use the `name` parameter to locate the custom object creator. + static public func createJSONConvertibleObject(name: String, values:[String:Any]) -> JSONConvertibleObject? { + guard let creator = JSONDecoding.jsonDecodableRegistry[name] else { + return nil + } + let jsonObj = creator() + jsonObj.setJSONValues(values) + return jsonObj + } +} + +/// Protocol for an object which can be converted into JSON text. +public protocol JSONConvertible { + /// Returns the JSON encoded String for any JSONConvertible type. + func jsonEncodedString() throws -> String +} + +// !FIX! changed this to be a class due to Linux protocols failing 'as' tests. +// revisit this +/// Base for a custom object which can be converted to and from JSON. +open class JSONConvertibleObject: JSONConvertible { + /// Default initializer. + public init() {} + /// Get the JSON keys/value. + open func setJSONValues(_ values:[String:Any]) {} + /// Set the object properties based on the JSON keys/values. + open func getJSONValues() -> [String:Any] { return [String:Any]() } + /// Encode the object into JSON text + open func jsonEncodedString() throws -> String { + return try self.getJSONValues().jsonEncodedString() + } +} + +/// Get the JSON keys/values from a custom object. +public extension JSONConvertibleObject { + /// Get a named value from the Dictionary converting to the given type with a default value. + func getJSONValue(named namd: String, from:[String:Any], defaultValue: T) -> T { + return (from[namd] as? T) ?? defaultValue + } +} + +/// An error occurring during JSON conversion. +public enum JSONConversionError: Error { + /// The object did not suppport JSON conversion. + case notConvertible(Any?) + /// A provided key was not a String. + case invalidKey(Any) + /// The JSON text contained a syntax error. + case syntaxError +} + +private let jsonBackSlash = UnicodeScalar(UInt32(92))! +private let jsonBackSpace = UnicodeScalar(UInt32(8))! +private let jsonFormFeed = UnicodeScalar(UInt32(12))! +private let jsonLF = UnicodeScalar(UInt32(10))! +private let jsonCR = UnicodeScalar(UInt32(13))! +private let jsonTab = UnicodeScalar(UInt32(9))! +private let jsonQuoteDouble = UnicodeScalar(UInt32(34))! + +private let jsonOpenObject = UnicodeScalar(UInt32(123))! +private let jsonOpenArray = UnicodeScalar(UInt32(91))! +private let jsonCloseObject = UnicodeScalar(UInt32(125))! +private let jsonCloseArray = UnicodeScalar(UInt32(93))! +private let jsonWhiteSpace = UnicodeScalar(UInt32(32))! +private let jsonColon = UnicodeScalar(UInt32(58))! +private let jsonComma = UnicodeScalar(UInt32(44))! + +private let highSurrogateLowerBound = UInt32(strtoul("d800", nil, 16)) +private let highSurrogateUpperBound = UInt32(strtoul("dbff", nil, 16)) +private let lowSurrogateLowerBound = UInt32(strtoul("dc00", nil, 16)) +private let lowSurrogateUpperBound = UInt32(strtoul("dfff", nil, 16)) +private let surrogateStep = UInt32(strtoul("400", nil, 16)) +private let surrogateBase = UInt32(strtoul("10000", nil, 16)) + +/// This is a stand-in for a JSON null. +/// May be produced when decoding. +public struct JSONConvertibleNull: JSONConvertible { + public func jsonEncodedString() throws -> String { + return "null" + } +} + +extension String: JSONConvertible { + /// Convert a String into JSON text + public func jsonEncodedString() throws -> String { + var s = "\"" + for uchar in self.unicodeScalars { + switch(uchar) { + case jsonBackSlash: + s.append("\\\\") + case jsonQuoteDouble: + s.append("\\\"") + case jsonBackSpace: + s.append("\\b") + case jsonFormFeed: + s.append("\\f") + case jsonLF: + s.append("\\n") + case jsonCR: + s.append("\\r") + case jsonTab: + s.append("\\t") + default: + s.append(String(uchar)) + } + } + s.append("\"") + return s + } +} + +extension Int: JSONConvertible { + /// Convert an Int into JSON text. + public func jsonEncodedString() throws -> String { + return String(self) + } +} + +extension UInt: JSONConvertible { + /// Convert a UInt into JSON text. + public func jsonEncodedString() throws -> String { + return String(self) + } +} + +extension Int32: JSONConvertible { + /// Convert an Int into JSON text. + public func jsonEncodedString() throws -> String { + return String(self) + } +} + +extension Int64: JSONConvertible { + /// Convert an Int into JSON text. + public func jsonEncodedString() throws -> String { + return String(self) + } +} + +extension UInt32: JSONConvertible { + /// Convert an Int into JSON text. + public func jsonEncodedString() throws -> String { + return String(self) + } +} + +extension UInt64: JSONConvertible { + /// Convert an Int into JSON text. + public func jsonEncodedString() throws -> String { + return String(self) + } +} + +extension Double: JSONConvertible { + /// Convert a Double into JSON text. + public func jsonEncodedString() throws -> String { + return String(self) + } +} + +extension Optional: JSONConvertible { + /// Convert an Optional into JSON text. + public func jsonEncodedString() throws -> String { + if self == nil { + return "null" + } else if let v = self! as? JSONConvertible { + return try v.jsonEncodedString() + } + throw JSONConversionError.notConvertible(self) + } +} + +extension Bool: JSONConvertible { + /// Convert a Bool into JSON text. + public func jsonEncodedString() throws -> String { + return self ? "true" : "false" + } +} + +// !FIX! Downcasting to protocol does not work on Linux +// Not sure if this is intentional, or a bug. +func jsonEncodedStringWorkAround(_ o: Any) throws -> String { + switch o { + case let jsonAble as JSONConvertibleObject: // as part of Linux work around + return try jsonAble.jsonEncodedString() + case let jsonAble as JSONConvertible: + return try jsonAble.jsonEncodedString() + case let jsonAble as String: + return try jsonAble.jsonEncodedString() + case let jsonAble as Int: + return try jsonAble.jsonEncodedString() + case let jsonAble as UInt: + return try jsonAble.jsonEncodedString() + case let jsonAble as Double: + return try jsonAble.jsonEncodedString() + case let jsonAble as Bool: + return try jsonAble.jsonEncodedString() + case let jsonAble as [Any]: + return try jsonAble.jsonEncodedString() + case let jsonAble as [[String:Any]]: + return try jsonAble.jsonEncodedString() + case let jsonAble as [String:Any]: + return try jsonAble.jsonEncodedString() + default: + throw JSONConversionError.notConvertible(o) + } +} + +extension Array: JSONConvertible { + /// Convert an Array into JSON text. + public func jsonEncodedString() throws -> String { + let s = try map { + try jsonEncodedStringWorkAround($0) + }.joined(separator: ",") + return "[\(s)]" + } +} + +extension Dictionary: JSONConvertible { + /// Convert a Dictionary into JSON text. + public func jsonEncodedString() throws -> String { + var s = "{" + var first = true + for (k, v) in self { + guard let strKey = k as? String else { + throw JSONConversionError.invalidKey(k) + } + if !first { + s.append(",") + } else { + first = false + } + s.append(try strKey.jsonEncodedString()) + s.append(":") + s.append(try jsonEncodedStringWorkAround(v)) + } + s.append("}") + return s + } +} + +extension String { + /// Decode the object represented by the JSON text. + public func jsonDecode() throws -> JSONConvertible { + let state = JSONDecodeState() + state.g = self.unicodeScalars.makeIterator() + + let o = try state.readObject() + if let _ = o as? JSONDecodeState.EOF { + throw JSONConversionError.syntaxError + } + return o + } +} + +private class JSONDecodeState { + struct EOF: JSONConvertible { + func jsonEncodedString() throws -> String { return "" } + } + + var g = String().unicodeScalars.makeIterator() + var pushBack: UnicodeScalar? + + func movePastWhite() { + while let c = self.next() { + if !c.isWhiteSpace() { + self.pushBack = c + break + } + } + } + + func readObject() throws -> JSONConvertible { + self.movePastWhite() + + guard let c = self.next() else { + return EOF() + } + + switch(c) { + case jsonOpenArray: + var a = [Any]() + self.movePastWhite() + guard let c = self.next() else { + throw JSONConversionError.syntaxError + } + if c != jsonCloseArray { + self.pushBack = c + while true { + a.append(try readObject()) + self.movePastWhite() + guard let c = self.next() else { + throw JSONConversionError.syntaxError + } + if c == jsonCloseArray { + break + } + if c != jsonComma { + throw JSONConversionError.syntaxError + } + } + } + return a + case jsonOpenObject: + var d = [String:Any]() + self.movePastWhite() + guard let c = self.next() else { + throw JSONConversionError.syntaxError + } + if c != jsonCloseObject { + self.pushBack = c + while true { + guard let key = try readObject() as? String else { + throw JSONConversionError.syntaxError + } + self.movePastWhite() + guard let c = self.next() else { + throw JSONConversionError.syntaxError + } + guard c == jsonColon else { + throw JSONConversionError.syntaxError + } + self.movePastWhite() + d[key] = try readObject() + do { + self.movePastWhite() + guard let c = self.next() else { + throw JSONConversionError.syntaxError + } + if c == jsonCloseObject { + break + } + if c != jsonComma { + throw JSONConversionError.syntaxError + } + } + } + } + if let objid = d[JSONDecoding.objectIdentifierKey] as? String { + if let o = JSONDecoding.createJSONConvertibleObject(name: objid, values: d) { + return o + } + } + return d + case jsonQuoteDouble: + return try readString() + default: + if c.isWhiteSpace() { + // nothing + } else if c.isDigit() || c == "-" || c == "+" { + return try readNumber(firstChar: c) + } else if c == "t" || c == "T" { + return try readTrue() + } else if c == "f" || c == "F" { + return try readFalse() + } else if c == "n" || c == "N" { + try readNull() + return JSONConvertibleNull() + } + } + throw JSONConversionError.syntaxError + } + + func next() -> UnicodeScalar? { + if pushBack != nil { + let c = pushBack! + pushBack = nil + return c + } + return g.next() + } + + // the opening quote has been read + func readString() throws -> String { + var next = self.next() + var esc = false + var s = "" + while let c = next { + + if esc { + switch(c) { + case jsonBackSlash: + s.append(String(jsonBackSlash)) + case jsonQuoteDouble: + s.append(String(jsonQuoteDouble)) + case "b": + s.append(String(jsonBackSpace)) + case "f": + s.append(String(jsonFormFeed)) + case "n": + s.append(String(jsonLF)) + case "r": + s.append(String(jsonCR)) + case "t": + s.append(String(jsonTab)) + case "u": + var hexStr = "" + for _ in 1...4 { + next = self.next() + guard let hexC = next else { + throw JSONConversionError.syntaxError + } + guard hexC.isHexDigit() else { + throw JSONConversionError.syntaxError + } + hexStr.append(String(hexC)) + } + var uint32Value = UInt32(strtoul(hexStr, nil, 16)) + // if unicode is a high/low surrogate, it can't be converted directly by UnicodeScalar + // if it's a low surrogate (not expected), throw error + if case lowSurrogateLowerBound...lowSurrogateUpperBound = uint32Value { + throw JSONConversionError.syntaxError + } + // if it's a high surrogate, find the low surrogate which the next unicode is supposed to be, then calculate the pair + if case highSurrogateLowerBound...highSurrogateUpperBound = uint32Value { + let highSurrogateValue = uint32Value + guard self.next() == jsonBackSlash else { + throw JSONConversionError.syntaxError + } + guard self.next() == "u" else { + throw JSONConversionError.syntaxError + } + var lowSurrogateHexStr = "" + for _ in 1...4 { + next = self.next() + guard let hexC = next else { + throw JSONConversionError.syntaxError + } + guard hexC.isHexDigit() else { + throw JSONConversionError.syntaxError + } + lowSurrogateHexStr.append(String(hexC)) + } + let lowSurrogateValue = UInt32(strtoul(lowSurrogateHexStr, nil, 16)) + uint32Value = ( highSurrogateValue - highSurrogateLowerBound ) * surrogateStep + ( lowSurrogateValue - lowSurrogateLowerBound ) + surrogateBase + } + if let result = UnicodeScalar(uint32Value) { + s.append(String(Character(result))) + } + default: + s.append(String(c)) + } + esc = false + } else if c == jsonBackSlash { + esc = true + } else if c == jsonQuoteDouble { + return s + } else { + s.append(String(c)) + } + + next = self.next() + } + throw JSONConversionError.syntaxError + } + + func readNumber(firstChar first: UnicodeScalar) throws -> JSONConvertible { + var s = "" + var needPeriod = true, needExp = true + s.append(String(Character(first))) + + if first == "." { + needPeriod = false + } + + var next = self.next() + var last = first + while let c = next { + if c.isDigit() { + s.append(String(c)) + } else if c == "." && !needPeriod { + break + } else if (c == "e" || c == "E") && !needExp { + break + } else if c == "." { + needPeriod = false + s.append(String(c)) + } else if c == "e" || c == "E" { + needExp = false + s.append(String(c)) + + next = self.next() + if next != nil && (next! == "-" || next! == "+") { + s.append(String(next!)) + } else { + pushBack = next! + } + + } else if last.isDigit() { + pushBack = c + if needPeriod && needExp { + return Int(s)! + } + return Double(s)! + } else { + break + } + last = c + next = self.next() + } + + throw JSONConversionError.syntaxError + } + + func readTrue() throws -> Bool { + var next = self.next() + if next != "r" && next != "R" { + throw JSONConversionError.syntaxError + } + next = self.next() + if next != "u" && next != "U" { + throw JSONConversionError.syntaxError + } + next = self.next() + if next != "e" && next != "E" { + throw JSONConversionError.syntaxError + } + next = self.next() + guard next != nil && !next!.isAlphaNum() else { + throw JSONConversionError.syntaxError + } + pushBack = next! + return true + } + + func readFalse() throws -> Bool { + var next = self.next() + if next != "a" && next != "A" { + throw JSONConversionError.syntaxError + } + next = self.next() + if next != "l" && next != "L" { + throw JSONConversionError.syntaxError + } + next = self.next() + if next != "s" && next != "S" { + throw JSONConversionError.syntaxError + } + next = self.next() + if next != "e" && next != "E" { + throw JSONConversionError.syntaxError + } + next = self.next() + guard next != nil && !next!.isAlphaNum() else { + throw JSONConversionError.syntaxError + } + pushBack = next! + return false + } + + func readNull() throws { + var next = self.next() + if next != "u" && next != "U" { + throw JSONConversionError.syntaxError + } + next = self.next() + if next != "l" && next != "L" { + throw JSONConversionError.syntaxError + } + next = self.next() + if next != "l" && next != "L" { + throw JSONConversionError.syntaxError + } + next = self.next() + guard next != nil && !next!.isAlphaNum() else { + throw JSONConversionError.syntaxError + } + pushBack = next! + } +} diff --git a/Sources/PerfectLib/Log.swift b/Sources/PerfectLib/Log.swift new file mode 100644 index 00000000..671c0672 --- /dev/null +++ b/Sources/PerfectLib/Log.swift @@ -0,0 +1,156 @@ +// +// LogManager.swift +// PerfectLib +// +// Created by Kyle Jessup on 7/21/15. +// Copyright (C) 2015 PerfectlySoft, Inc. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +#if os(Linux) + import SwiftGlibc + import LinuxBridge +#else + import Darwin +#endif + +/// Placeholder functions for logging system +public protocol Logger { + func debug(message: String) + func info(message: String) + func warning(message: String) + func error(message: String) + func critical(message: String) + func terminal(message: String) +} + +public struct ConsoleLogger: Logger { + public init(){} + + public func debug(message: String) { + print("[DBG] " + message) + } + + public func info(message: String) { + print("[INFO] " + message) + } + + public func warning(message: String) { + print("[WARN] " + message) + } + + public func error(message: String) { + print("[ERR] " + message) + } + + public func critical(message: String) { + print("[CRIT] " + message) + } + + public func terminal(message: String) { + print("[TERM] " + message) + } +} + +public struct SysLogger: Logger { + let consoleEcho = ConsoleLogger() + public init(){} + + func syslog(priority: Int32, _ args: CVarArg...) { + withVaList(args) { + vsyslog(priority, "%s", $0) + } + } + + public func debug(message: String) { + consoleEcho.debug(message: message) + message.withCString { + f in + syslog(priority: LOG_DEBUG, f) + } + } + + public func info(message: String) { + consoleEcho.info(message: message) + message.withCString { + f in + syslog(priority: LOG_INFO, f) + } + } + + public func warning(message: String) { + consoleEcho.warning(message: message) + message.withCString { + f in + syslog(priority: LOG_WARNING, f) + } + } + + public func error(message: String) { + consoleEcho.error(message: message) + message.withCString { + f in + syslog(priority: LOG_ERR, f) + } + } + + public func critical(message: String) { + consoleEcho.critical(message: message) + message.withCString { + f in + syslog(priority: LOG_CRIT, f) + } + } + + public func terminal(message: String) { + consoleEcho.terminal(message: message) + message.withCString { + f in + syslog(priority: LOG_EMERG, f) + } + } +} + +/// Placeholder functions for logging system +public struct Log { + private init(){} + + public static var logger: Logger = ConsoleLogger() + + public static func debug(message: @autoclosure () -> String) { +// #if DEBUG + Log.logger.debug(message: message()) +// #endif + } + + public static func info(message: String) { + Log.logger.info(message: message) + } + + public static func warning(message: String) { + Log.logger.warning(message: message) + } + + public static func error(message: String) { + Log.logger.error(message: message) + } + + public static func critical(message: String) { + Log.logger.critical(message: message) + } + + public static func terminal(message: String) -> Never { + Log.logger.terminal(message: message) + fatalError(message) + } +} diff --git a/Sources/PerfectLib/PerfectError.swift b/Sources/PerfectLib/PerfectError.swift new file mode 100644 index 00000000..ef3aa7e6 --- /dev/null +++ b/Sources/PerfectLib/PerfectError.swift @@ -0,0 +1,70 @@ +// +// PerfectError.swift +// PerfectLib +// +// Created by Kyle Jessup on 7/5/15. +// Copyright (C) 2015 PerfectlySoft, Inc. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +#if os(Linux) +import SwiftGlibc + +var errno: Int32 { + return __errno_location().pointee +} +#else +import Darwin +#endif + +/// Some but not all of the exception types which may be thrown by the system +public enum PerfectError : Error { + /// A network related error code and message. + case networkError(Int32, String) + /// A file system related error code and message. + case fileError(Int32, String) + /// A OS level error code and message. + case systemError(Int32, String) + /// An API exception error message. + case apiError(String) +} + + +func ThrowFileError(file: String = #file, function: String = #function, line: Int = #line) throws -> Never { + let err = errno + let msg = String(validatingUTF8: strerror(err))! + +// print("FileError: \(err) \(msg)") + + throw PerfectError.fileError(err, msg + " \(file) \(function) \(line)") +} + + +func ThrowSystemError(file: String = #file, function: String = #function, line: Int = #line) throws -> Never { + let err = errno + let msg = String(validatingUTF8: strerror(err))! + +// print("SystemError: \(err) \(msg)") + + throw PerfectError.systemError(err, msg + " \(file) \(function) \(line)") +} + + +func ThrowNetworkError(file: String = #file, function: String = #function, line: Int = #line) throws -> Never { + let err = errno + let msg = String(validatingUTF8: strerror(err))! + +// print("NetworkError: \(err) \(msg)") + + throw PerfectError.networkError(err, msg + " \(file) \(function) \(line)") +} diff --git a/Sources/PerfectLib/PerfectServer.swift b/Sources/PerfectLib/PerfectServer.swift new file mode 100644 index 00000000..18c7b8de --- /dev/null +++ b/Sources/PerfectLib/PerfectServer.swift @@ -0,0 +1,50 @@ +// +// Perfect.swift +// PerfectLib +// +// Created by Kyle Jessup on 7/5/15. +// Copyright (C) 2015 PerfectlySoft, Inc. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +#if os(Linux) + import SwiftGlibc +#else + import Darwin +#endif + +/// Provides access to various system level features for the process. +/// A static instance of this class is created at startup and all access to this object go through the `PerfectServer.staticPerfectServer` static property. +public struct PerfectServer { + + @available(*, deprecated, message: "No longer required to call this") + public static func initializeServices() { + + } + + /// Switch the current process to run with the permissions of the indicated user + public static func switchTo(userName unam: String) throws { + guard let pw = getpwnam(unam)?.pointee else { + try ThrowSystemError() + } + let gid = pw.pw_gid + let uid = pw.pw_uid + guard 0 == setgid(gid) else { + try ThrowSystemError() + } + guard 0 == setuid(uid) else { + try ThrowSystemError() + } + } +} + diff --git a/Sources/PerfectLib/SwiftCompatibility.swift b/Sources/PerfectLib/SwiftCompatibility.swift new file mode 100644 index 00000000..b0ba36f3 --- /dev/null +++ b/Sources/PerfectLib/SwiftCompatibility.swift @@ -0,0 +1,33 @@ +// +// SwiftCompatibility.swift +// PerfectLib +// +// Created by Kyle Jessup on 2016-04-22. +// Copyright © 2016 PerfectlySoft. All rights reserved. +// + +extension String { + func range(ofString string: String, ignoreCase: Bool = false) -> Range? { + var idx = self.startIndex + let endIdx = self.endIndex + + while idx != endIdx { + if ignoreCase ? (String(self[idx]).lowercased() == String(string[string.startIndex]).lowercased()) : (self[idx] == string[string.startIndex]) { + var newIdx = self.index(after: idx) + var findIdx = string.index(after: string.startIndex) + let findEndIdx = string.endIndex + + while newIdx != endIndex && findIdx != findEndIdx && (ignoreCase ? (String(self[newIdx]).lowercased() == String(string[findIdx]).lowercased()) : (self[newIdx] == string[findIdx])) { + newIdx = self.index(after: newIdx) + findIdx = string.index(after: findIdx) + } + + if findIdx == findEndIdx { // match + return idx..? + + let cArgsCount = args?.count ?? 0 + let cArgs = UnsafeMutablePointer.allocate(capacity: cArgsCount + 2) + + defer { + cArgs.deinitialize(count: cArgsCount + 2) + cArgs.deallocate(capacity: cArgsCount + 2) + } + + cArgs[0] = strdup(cmd) + cArgs[cArgsCount + 1] = nil + + for idx in 0...allocate(capacity: cEnvCount + 1) + + defer { cEnv.deinitialize(count: cEnvCount + 1) ; cEnv.deallocate(capacity: cEnvCount + 1) } + + cEnv[cEnvCount] = nil + for idx in 0.. Bool { + return self.pid != -1 + } + + /// Terminate the process and clean up. + public func close() { + if self.stdin != nil { + self.stdin!.close() + } + if self.stdout != nil { + self.stdout!.close() + } + if self.stderr != nil { + self.stderr!.close() + } + if self.pid != -1 { + do { + let _ = try self.kill() + } catch { + + } + } + self.stdin = nil + self.stdout = nil + self.stderr = nil + self.pid = -1 + } + + /// Detach from the process such that it will not be manually terminated when this object is deinitialized. + public func detach() { + self.pid = -1 + } + + /// Determine if the process has completed running and retrieve its result code. + public func wait(hang: Bool = true) throws -> Int32 { + var code = Int32(0) + while true { + let status = waitpid(self.pid, &code, WUNTRACED | (hang ? 0 : WNOHANG)) + if status == -1 && errno == EINTR { + continue + } + guard status != -1 else { + try ThrowSystemError() + } + break + } + self.pid = -1 + return (((code) & 0xff00) >> 8) + } + + /// Terminate the process and return its result code. + public func kill(signal: Int32 = SIGTERM) throws -> Int32 { + #if os(Linux) + let status = SwiftGlibc.kill(self.pid, signal) + #else + let status = Darwin.kill(self.pid, signal) + #endif + guard status != -1 else { + try ThrowSystemError() + } + return try self.wait() + } +} diff --git a/Sources/PerfectLib/Utilities.swift b/Sources/PerfectLib/Utilities.swift new file mode 100644 index 00000000..4e2b9639 --- /dev/null +++ b/Sources/PerfectLib/Utilities.swift @@ -0,0 +1,692 @@ +// +// Utilities.swift +// PerfectLib +// +// Created by Kyle Jessup on 7/17/15. +// Copyright (C) 2015 PerfectlySoft, Inc. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +import Foundation + +#if os(Linux) +import LinuxBridge +#else +import Darwin +#endif + +/// This class permits an UnsafeMutablePointer to be used as a GeneratorType +public struct GenerateFromPointer : IteratorProtocol { + + public typealias Element = T + + private(set) var count = 0 + private(set) var pos = 0 + private(set) var from: UnsafeMutablePointer + + /// Initialize given an UnsafeMutablePointer and the number of elements pointed to. + public init(from: UnsafeMutablePointer, count: Int) { + self.from = from + self.count = count + } + + /// Return the next element or nil if the sequence has been exhausted. + mutating public func next() -> Element? { + guard count > 0 else { + return nil + } + self.count -= 1 + let result = self.from[self.pos] + self.pos += 1 + return result + } +} + +/// A generalized wrapper around the Unicode codec operations. +public struct Encoding { + + /// Return a String given a character generator. + public static func encode(codec inCodec: D, generator: G) -> String where G.Element == D.CodeUnit, G.Element == D.CodeUnit { + var encodedString = "" + var finished: Bool = false + var mutableDecoder = inCodec + var mutableGenerator = generator + repeat { + let decodingResult = mutableDecoder.decode(&mutableGenerator) + switch decodingResult { + case .scalarValue(let char): + encodedString.append(String(char)) + case .emptyInput: + finished = true + /* ignore errors and unexpected values */ + case .error: + finished = true + } + } while !finished + return encodedString + } +} + +/// Utility wrapper permitting a UTF-8 character generator to encode a String. Also permits a String to be converted into a UTF-8 byte array. +public struct UTF8Encoding { + + /// Use a character generator to create a String. + public static func encode(generator gen: G) -> String where G.Element == UTF8.CodeUnit { + return Encoding.encode(codec: UTF8(), generator: gen) + } + + /// Use a character sequence to create a String. + public static func encode(bytes byts: S) -> String where S.Iterator.Element == UTF8.CodeUnit { + return encode(generator: byts.makeIterator()) + } + + /// Use a character sequence to create a String. + public static func encode(bytes byts: [UTF8.CodeUnit]) -> String { + return encode(generator: GenerateFromPointer(from: UnsafeMutablePointer(mutating: byts), count: byts.count)) + } + + /// Decode a String into an array of UInt8. + public static func decode(string str: String) -> [UInt8] { + return [UInt8](str.utf8) + } +} + +extension UInt8 { + var shouldURLEncode: Bool { + switch self { + case 0..<33, 34..<38, 43, 58, 60, 62, + 91..<95, 96, 123..<127, 128...UInt8.max: + return true + default: + return false + } + } + + // same as String(self, radix: 16) + // but outputs two characters. i.e. 0 padded + var hexString: String { + + var s = "" + let b = self >> 4 + s.append(String(Character(UnicodeScalar(b > 9 ? b - 10 + 65 : b + 48)))) + let b2 = self & 0x0F + s.append(String(Character(UnicodeScalar(b2 > 9 ? b2 - 10 + 65 : b2 + 48)))) + return s + } +} + +extension String { + /// Returns the String with all special HTML characters encoded. + public var stringByEncodingHTML: String { + var ret = "" + var g = self.unicodeScalars.makeIterator() + var lastWasCR = false + while let c = g.next() { + if c == UnicodeScalar(10) { + if lastWasCR { + lastWasCR = false + ret.append("\n") + } else { + ret.append("
\n") + } + continue + } else if c == UnicodeScalar(13) { + lastWasCR = true + ret.append("
\r") + continue + } + lastWasCR = false + if c < UnicodeScalar(0x0009) { + if let scale = UnicodeScalar(0x0030 + UInt32(c)) { + ret.append("&#x") + ret.append(String(Character(scale))) + ret.append(";") + } + } else if c == UnicodeScalar(0x0022) { + ret.append(""") + } else if c == UnicodeScalar(0x0026) { + ret.append("&") + } else if c == UnicodeScalar(0x0027) { + ret.append("'") + } else if c == UnicodeScalar(0x003C) { + ret.append("<") + } else if c == UnicodeScalar(0x003E) { + ret.append(">") + } else if c > UnicodeScalar(126) { + ret.append("&#\(UInt32(c));") + } else { + ret.append(String(Character(c))) + } + } + return ret + } + + /// Returns the String with all special URL characters encoded. + public var stringByEncodingURL: String { + var ret = "" + var g = self.utf8.makeIterator() + while let c = g.next() { + if c.shouldURLEncode { + ret.append(String(Character(UnicodeScalar(37)))) + ret.append(c.hexString) + } else { + ret.append(String(Character(UnicodeScalar(c)))) + } + } + return ret + } + + // Utility - not sure if it makes the most sense to have here or outside or elsewhere + static func byteFromHexDigits(one c1v: UInt8, two c2v: UInt8) -> UInt8? { + + let capA: UInt8 = 65 + let capF: UInt8 = 70 + let lowA: UInt8 = 97 + let lowF: UInt8 = 102 + let zero: UInt8 = 48 + let nine: UInt8 = 57 + + var newChar = UInt8(0) + + if c1v >= capA && c1v <= capF { + newChar = c1v - capA + 10 + } else if c1v >= lowA && c1v <= lowF { + newChar = c1v - lowA + 10 + } else if c1v >= zero && c1v <= nine { + newChar = c1v - zero + } else { + return nil + } + + newChar *= 16 + + if c2v >= capA && c2v <= capF { + newChar += c2v - capA + 10 + } else if c2v >= lowA && c2v <= lowF { + newChar += c2v - lowA + 10 + } else if c2v >= zero && c2v <= nine { + newChar += c2v - zero + } else { + return nil + } + return newChar + } + + /// Decode the % encoded characters in a URL and return result + public var stringByDecodingURL: String? { + + let percent: UInt8 = 37 + let plus: UInt8 = 43 + let space: UInt8 = 32 + + var bytesArray = [UInt8]() + + var g = self.utf8.makeIterator() + while let c = g.next() { + if c == percent { + guard let c1v = g.next() else { + return nil + } + guard let c2v = g.next() else { + return nil + } + guard let newChar = String.byteFromHexDigits(one: c1v, two: c2v) else { + return nil + } + bytesArray.append(newChar) + } else if c == plus { + bytesArray.append(space) + } else { + bytesArray.append(c) + } + } + return UTF8Encoding.encode(bytes: bytesArray) + } + + /// Decode a hex string into resulting byte array + public var decodeHex: [UInt8]? { + + var bytesArray = [UInt8]() + var g = self.utf8.makeIterator() + while let c1v = g.next() { + + guard let c2v = g.next() else { + return nil + } + + guard let newChar = String.byteFromHexDigits(one: c1v, two: c2v) else { + return nil + } + + bytesArray.append(newChar) + } + return bytesArray + } +} + +public struct UUID { + let uuid: uuid_t + + public init() { + let u = UnsafeMutablePointer.allocate(capacity: MemoryLayout.size) + defer { + u.deallocate(capacity: MemoryLayout.size) + } + uuid_generate_random(u) + self.uuid = UUID.uuidFromPointer(u) + } + + public init(_ string: String) { + let u = UnsafeMutablePointer.allocate(capacity: MemoryLayout.size) + defer { + u.deallocate(capacity: MemoryLayout.size) + } + uuid_parse(string, u) + self.uuid = UUID.uuidFromPointer(u) + } + + init(_ uuid: uuid_t) { + self.uuid = uuid + } + + private static func uuidFromPointer(_ u: UnsafeMutablePointer) -> uuid_t { + // is there a better way? + return uuid_t(u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7], u[8], u[9], u[10], u[11], u[12], u[13], u[14], u[15]) + } + + public var string: String { + let u = UnsafeMutablePointer.allocate(capacity: MemoryLayout.size) + let unu = UnsafeMutablePointer.allocate(capacity: 37) // as per spec. 36 + null + defer { + u.deallocate(capacity: MemoryLayout.size) + unu.deallocate(capacity: 37) + } + var uu = self.uuid + memcpy(u, &uu, MemoryLayout.size) + uuid_unparse_lower(u, unu) + return String(validatingUTF8: unu)! + } +} + +extension String { + + @available(*, unavailable, message: "Use UUID(_:String)") + public func asUUID() -> uuid_t { + return UUID(self).uuid + } + + @available(*, unavailable, message: "Use UUID.string") + public static func fromUUID(uuid: uuid_t) -> String { + return UUID(uuid).string + } +} + +@available(*, unavailable, renamed: "UUID()") +public func random_uuid() -> uuid_t { + return UUID().uuid +} + +extension String { + + /// Parse an HTTP Digest authentication header returning a Dictionary containing each part. + public func parseAuthentication() -> [String:String] { + var ret = [String:String]() + if let _ = self.range(ofString: "Digest ") { + ret["type"] = "Digest" + let wantFields = ["username", "nonce", "nc", "cnonce", "response", "uri", "realm", "qop", "algorithm"] + for field in wantFields { + if let foundField = String.extractField(from: self, named: field) { + ret[field] = foundField + } + } + } + return ret + } + + private static func extractField(from frm: String, named: String) -> String? { + guard let range = frm.range(ofString: named + "=") else { + return nil + } + + var currPos = range.upperBound + var ret = "" + let quoted = frm[currPos] == "\"" + if quoted { + currPos = frm.index(after: currPos) + let tooFar = frm.endIndex + while currPos != tooFar { + if frm[currPos] == "\"" { + break + } + ret.append(frm[currPos]) + currPos = frm.index(after: currPos) + } + } else { + let tooFar = frm.endIndex + while currPos != tooFar { + if frm[currPos] == "," { + break + } + ret.append(frm[currPos]) + currPos = frm.index(after: currPos) + } + } + return ret + } +} + +extension String { + + /// Replace all occurrences of `string` with `withString`. + public func stringByReplacing(string strng: String, withString: String) -> String { + + guard !strng.isEmpty else { + return self + } + guard !self.isEmpty else { + return self + } + + var ret = "" + var idx = self.startIndex + let endIdx = self.endIndex + + while idx != endIdx { + if self[idx] == strng[strng.startIndex] { + var newIdx = self.index(after: idx) + var findIdx = strng.index(after: strng.startIndex) + let findEndIdx = strng.endIndex + + while newIdx != endIndex && findIdx != findEndIdx && self[newIdx] == strng[findIdx] { + newIdx = self.index(after: newIdx) + findIdx = strng.index(after: findIdx) + } + + if findIdx == findEndIdx { // match + ret.append(withString) + idx = newIdx + continue + } + } + ret.append(self[idx]) + idx = self.index(after: idx) + } + + return ret + } + + // For compatibility due to shifting swift + public func contains(string strng: String) -> Bool { + return nil != self.range(ofString: strng) + } +} + +extension String { + func begins(with str: String) -> Bool { + return self.characters.starts(with: str.characters) + } + + func ends(with str: String) -> Bool { + let mine = self.characters + let theirs = str.characters + + guard mine.count >= theirs.count else { + return false + } + + return str.begins(with: self[self.index(self.endIndex, offsetBy: -theirs.count).. Double { + + var posixTime = timeval() + gettimeofday(&posixTime, nil) + return Double((posixTime.tv_sec * 1000) + (Int(posixTime.tv_usec)/1000)) +} +/// Converts the milliseconds based ICU date to seconds since the epoch +public func icuDateToSeconds(_ icuDate: Double) -> Int { + return Int(icuDate / 1000) +} +/// Converts the seconds since the epoch into the milliseconds based ICU date +public func secondsToICUDate(_ seconds: Int) -> Double { + return Double(seconds * 1000) +} + +/// Format a date value according to the indicated format string and return a date string. +/// - parameter date: The date value +/// - parameter format: The format by which the date will be formatted. Use a valid strftime style format string. +/// - parameter timezone: The optional timezone in which the date is expected to be based. Default is the local timezone. +/// - parameter locale: The optional locale which will be used when parsing the date. Default is the current global locale. +/// - returns: The resulting date string +/// - throws: `PerfectError.systemError` +public func formatDate(_ date: Double, format: String, timezone inTimezone: String? = nil, locale inLocale: String? = nil) throws -> String { + + var t = tm() + var time = time_t(date / 1000.0) + gmtime_r(&time, &t) + let maxResults = 1024 + let results = UnsafeMutablePointer.allocate(capacity: maxResults) + defer { + results.deallocate(capacity: maxResults) + } + let res = strftime(results, maxResults, format, &t) + if res > 0 { + let formatted = String(validatingUTF8: results) + return formatted! + } + try ThrowSystemError() +} + +extension UnicodeScalar { + + /// Returns true if the UnicodeScalar is a white space character + public func isWhiteSpace() -> Bool { + return isspace(Int32(self.value)) != 0 + } + /// Returns true if the UnicodeScalar is a digit character + public func isDigit() -> Bool { + return isdigit(Int32(self.value)) != 0 + } + /// Returns true if the UnicodeScalar is an alpha-numeric character + public func isAlphaNum() -> Bool { + return isalnum(Int32(self.value)) != 0 + } + /// Returns true if the UnicodeScalar is a hexadecimal character + public func isHexDigit() -> Bool { + if self.isDigit() { + return true + } + switch self { + case "A", "B", "C", "D", "E", "F", "a", "b", "c", "d", "e", "f": + return true + default: + return false + } + } +} + +//public extension NetNamedPipe { +// /// Send the existing & opened `File`'s descriptor over the connection to the recipient +// /// - parameter file: The `File` whose descriptor to send +// /// - parameter callBack: The callback to call when the send completes. The parameter passed will be `true` if the send completed without error. +// /// - throws: `PerfectError.NetworkError` +// public func sendFile(_ file: File, callBack: @escaping (Bool) -> ()) throws { +// try self.sendFd(Int32(file.fd), callBack: callBack) +// } +// +// /// Receive an existing opened `File` descriptor from the sender +// /// - parameter callBack: The callback to call when the receive completes. The parameter passed will be the received `File` object or nil. +// /// - throws: `PerfectError.NetworkError` +// public func receiveFile(callBack: @escaping (File?) -> ()) throws { +// try self.receiveFd { +// fd in +// +// if fd == invalidSocket { +// callBack(nil) +// } else { +// callBack(File("", fd: fd)) +// } +// } +// } +//} +// +//import OpenSSL +// +//extension String.UTF8View { +// var sha1: [UInt8] { +// let bytes = UnsafeMutablePointer.allocate(capacity: Int(SHA_DIGEST_LENGTH)) +// defer { bytes.deallocate(capacity: Int(SHA_DIGEST_LENGTH)) } +// +// SHA1(Array(self), (self.count), bytes) +// +// var r = [UInt8]() +// for idx in 0.. 0 else { + return false + } + return unis[unis.startIndex] == Character(filePathSeparator) + } + + public var endsWithFilePathSeparator: Bool { + let unis = self.characters + guard unis.count > 0 else { + return false + } + return unis[unis.index(before: unis.endIndex)] == Character(filePathSeparator) + } + + private func filePathComponents(addFirstLast addfl: Bool) -> [String] { + var r = [String]() + let unis = self.characters + guard unis.count > 0 else { + return r + } + + if addfl && self.beginsWithFilePathSeparator { + r.append(String(filePathSeparator)) + } + + r.append(contentsOf: self.characters.split(separator: Character(filePathSeparator)).map { String($0) }) + + if addfl && self.endsWithFilePathSeparator { + if !self.beginsWithFilePathSeparator || r.count > 1 { + r.append(String(filePathSeparator)) + } + } + return r + } + + public var filePathComponents: [String] { + return self.filePathComponents(addFirstLast: true) + } + + public var lastFilePathComponent: String { + let last = self.filePathComponents(addFirstLast: false).last ?? "" + if last.isEmpty && self.characters.first == Character(filePathSeparator) { + return String(filePathSeparator) + } + return last + } + + public var deletingLastFilePathComponent: String { + var comps = self.filePathComponents(addFirstLast: false) + guard comps.count > 1 else { + if self.beginsWithFilePathSeparator { + return String(filePathSeparator) + } + return "" + } + comps.removeLast() + let joined = comps.joined(separator: String(filePathSeparator)) + if self.beginsWithFilePathSeparator { + return String(filePathSeparator) + joined + } + return joined + } + + private func lastPathSeparator(in unis: String.CharacterView) -> String.CharacterView.Index { + let startIndex = unis.startIndex + var endIndex = unis.endIndex + while endIndex != startIndex { + if unis[unis.index(before: endIndex)] != Character(filePathSeparator) { + break + } + endIndex = unis.index(before: endIndex) + } + return endIndex + } + + private func lastExtensionSeparator(in unis: String.CharacterView, endIndex: String.CharacterView.Index) -> String.CharacterView.Index { + var endIndex = endIndex + while endIndex != startIndex { + endIndex = unis.index(before: endIndex) + if unis[endIndex] == Character(fileExtensionSeparator) { + break + } + } + return endIndex + } + + public var deletingFileExtension: String { + let unis = self.characters + let startIndex = unis.startIndex + var endIndex = lastPathSeparator(in: unis) + let noTrailsIndex = endIndex + endIndex = lastExtensionSeparator(in: unis, endIndex: endIndex) + guard endIndex != startIndex else { + if noTrailsIndex == startIndex { + return self + } + return self[startIndex.. [String : Any] { + return [JSONDecoding.objectIdentifierKey:Test.registerName, "One":1] + } + } + + JSONDecoding.registerJSONDecodable(name: Test.registerName, creator: { return Test() }) + + do { + let encoded = try Test().jsonEncodedString() + let decoded = try encoded.jsonDecode() as? Test + + XCTAssert(decoded != nil) + + XCTAssert(decoded!.one == 1) + } catch { + XCTAssert(false, "Exception \(error)") + } + } + + func testJSONConvertibleObject2() { + + class User: JSONConvertibleObject { + static let registerName = "user" + var firstName = "" + var lastName = "" + var age = 0 + override func setJSONValues(_ values: [String : Any]) { + self.firstName = getJSONValue(named: "firstName", from: values, defaultValue: "") + self.lastName = getJSONValue(named: "lastName", from: values, defaultValue: "") + self.age = getJSONValue(named: "age", from: values, defaultValue: 0) + } + override func getJSONValues() -> [String : Any] { + return [ + JSONDecoding.objectIdentifierKey:User.registerName, + "firstName":firstName, + "lastName":lastName, + "age":age + ] + } + } + + // register the class. do this once + JSONDecoding.registerJSONDecodable(name: User.registerName, creator: { return User() }) + + // encode and decode the object + let user = User() + user.firstName = "Donnie" + user.lastName = "Darko" + user.age = 17 + + do { + let encoded = try user.jsonEncodedString() + print(encoded) + + guard let user2 = try encoded.jsonDecode() as? User else { + return XCTAssert(false, "Invalid object \(encoded)") + } + + XCTAssert(user.firstName == user2.firstName) + XCTAssert(user.lastName == user2.lastName) + XCTAssert(user.age == user2.age) + } catch {} + } + + func testJSONEncodeDecode() { + + let srcAry: [[String:Any]] = [["i": -41451, "i2": 41451, "d": -42E+2, "t": true, "f": false, "n": nil as String?, "a":[1, 2, 3, 4]], ["another":"one"]] + var encoded = "" + var decoded: [Any]? + do { + + encoded = try srcAry.jsonEncodedString() + + } catch let e { + XCTAssert(false, "Exception while encoding JSON \(e)") + return + } + + do { + + decoded = try encoded.jsonDecode() as? [Any] + + } catch let e { + XCTAssert(false, "Exception while decoding JSON \(e)") + return + } + + XCTAssert(decoded != nil) + + let resAry = decoded! + + XCTAssert(srcAry.count == resAry.count) + + for index in 0.. 0) + + let waitRes = try proc.wait() + + XCTAssert(0 == waitRes, "\(waitRes) \(UTF8Encoding.encode(bytes: data))") + + proc.close() + } catch { + XCTAssert(false, "Exception running SysProcess test: \(error)") + } +#endif + } + + func testStringByEncodingHTML() { + let src = "\"quoted\" '& ☃" + let res = src.stringByEncodingHTML + XCTAssertEqual(res, "<b>"quoted" '& ☃") + } + + func testStringByEncodingURL() { + let src = "This has \"weird\" characters & ßtuff" + let res = src.stringByEncodingURL + XCTAssertEqual(res, "This%20has%20%22weird%22%20characters%20&%20%C3%9Ftuff") + } + + func testStringByDecodingURL() { + let src = "This has \"weird\" characters & ßtuff" + let mid = src.stringByEncodingURL + guard let res = mid.stringByDecodingURL else { + XCTAssert(false, "Got nil String") + return + } + XCTAssert(res == src, "Bad URL decoding") + } + + func testStringByDecodingURL2() { + let src = "This is badly%PWencoded" + let res = src.stringByDecodingURL + + XCTAssert(res == nil, "Bad URL decoding") + } + + func testStringByReplacingString() { + + let src = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ" + let test = "ABCFEDGHIJKLMNOPQRSTUVWXYZABCFEDGHIJKLMNOPQRSTUVWXYZABCFEDGHIJKLMNOPQRSTUVWXYZ" + let find = "DEF" + let rep = "FED" + + let res = src.stringByReplacing(string: find, withString: rep) + + XCTAssert(res == test) + } + + func testStringByReplacingString2() { + + let src = "" + let find = "DEF" + let rep = "FED" + + let res = src.stringByReplacing(string: find, withString: rep) + + XCTAssert(res == src) + } + + func testStringByReplacingString3() { + + let src = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ" + let find = "" + let rep = "FED" + + let res = src.stringByReplacing(string: find, withString: rep) + + XCTAssert(res == src) + } + + func testSubstringTo() { + + let src = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ" + let res = src.substring(to: src.index(src.startIndex, offsetBy: 5)) + + XCTAssert(res == "ABCDE") + } + + func testRangeTo() { + + let src = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + let res = src.range(ofString: "DEF") + XCTAssert(res == src.index(src.startIndex, offsetBy: 3)...size + XCTAssert(i16 == bytes2.export16Bits()) + XCTAssert(bytes2.availableExportBytes == 1) + XCTAssert(i8 == bytes2.export8Bits()) + } + + func testSymlink() { + let f1 = File("./foo") + let f2 = File("./foo2") + do { + f2.delete() + try f1.open(.truncate) + try f1.write(string: "test") + f1.close() + defer { + f1.delete() + } + + let newF2 = try f1.linkTo(path: f2.path) + + XCTAssert(try newF2.readString() == "test") + XCTAssert(newF2.isLink) + } catch { + XCTAssert(false, "\(error)") + } + } +} + +extension PerfectLibTests { + static var allTests : [(String, (PerfectLibTests) -> () throws -> Void)] { + return [ + ("testJSONConvertibleObject1", testJSONConvertibleObject1), + ("testJSONConvertibleObject2", testJSONConvertibleObject2), + ("testJSONEncodeDecode", testJSONEncodeDecode), + ("testJSONDecodeUnicode", testJSONDecodeUnicode), + ("testSysProcess", testSysProcess), + ("testStringByEncodingHTML", testStringByEncodingHTML), + ("testStringByEncodingURL", testStringByEncodingURL), + ("testStringByDecodingURL", testStringByDecodingURL), + ("testStringByDecodingURL2", testStringByDecodingURL2), + ("testStringByReplacingString", testStringByReplacingString), + ("testStringByReplacingString2", testStringByReplacingString2), + ("testStringByReplacingString3", testStringByReplacingString3), + ("testSubstringTo", testSubstringTo), + ("testRangeTo", testRangeTo), + ("testSubstringWith", testSubstringWith), + + ("testDeletingPathExtension", testDeletingPathExtension), + ("testGetPathExtension", testGetPathExtension), + + ("testDirCreate", testDirCreate), + ("testDirCreateRel", testDirCreateRel), + ("testDirForEach", testDirForEach), + + ("testFilePerms", testFilePerms), + ("testDirPerms", testDirPerms), + + ("testBytesIO", testBytesIO) + ] + } +} diff --git a/Tests/PerfectLibTests/XCTestManifests.swift b/Tests/PerfectLibTests/XCTestManifests.swift new file mode 100644 index 00000000..6edb6a7c --- /dev/null +++ b/Tests/PerfectLibTests/XCTestManifests.swift @@ -0,0 +1,27 @@ +// +// XCTestManifests.swift +// +// Created by Kyle Jessup on 2015-10-19. +// Copyright © 2015 PerfectlySoft. All rights reserved. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +import XCTest + +#if !os(OSX) +public func allTests() -> [XCTestCaseEntry] { + return [ + testCase(PerfectLibTests.allTests) + ] +} +#endif