Skip to content

Commit

Permalink
Using Site URL (#9)
Browse files Browse the repository at this point in the history
* fixes and new warnings (#7)

* adding logo and starting documentation

* renaming types and simplifying API

* correct WordPressSite

* fixing review issues

* adding BaseURLSite

---------

Co-authored-by: Ahmed Shendy <[email protected]>
  • Loading branch information
leogdion and devahmedshendy authored Aug 4, 2023
1 parent bff9c9c commit 36ffc69
Show file tree
Hide file tree
Showing 48 changed files with 1,091 additions and 631 deletions.
2 changes: 1 addition & 1 deletion .periphery.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
retain_public: true
targets:
- ContributeWordpress
- ContributeWordPress
8 changes: 4 additions & 4 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/brightdigit/Contribute.git",
"state" : {
"revision" : "958759a8857c55ee84d5504ad88e653d5e87c61f",
"version" : "1.0.0-alpha.1"
"revision" : "136c566f0f5c893547a8158878d15634d3652553",
"version" : "1.0.0-alpha.3"
}
},
{
"identity" : "syndikit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/brightdigit/SyndiKit.git",
"state" : {
"revision" : "238c8d85a94b67072c8fafcb4feff1b6df11d87b",
"version" : "0.3.3"
"revision" : "67f398a23ad1ca35a276295d4cf0e46c7cfc4e4f",
"version" : "0.3.4"
}
},
{
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ let package = Package(
dependencies: [
.package(
url: "https://github.com/brightdigit/Contribute.git",
from: "1.0.0-alpha.1"
from: "1.0.0-alpha.3"
),
.package(
url: "https://github.com/brightdigit/SyndiKit.git",
from: "0.3.3"
from: "0.3.4"
)
],
targets: [
Expand Down
138 changes: 136 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,136 @@
# ContributeWordPress
Import your WordPress site into Publish
<p align="center">
<img alt="ContributeWordPress" title="ContributeWordPress" src="Sources/ContributeWordPress/Documentation.docc/Resources/ContributeWordPressLogo.png" height="125">
</p>
<h1 align="center">ContributeWordPress</h1>

Import your WordPress site into Publish.

[![SwiftPM](https://img.shields.io/badge/SPM-Linux%20%7C%20iOS%20%7C%20macOS%20%7C%20watchOS%20%7C%20tvOS-success?logo=swift)](https://swift.org)
[![Twitter](https://img.shields.io/badge/[email protected]?style=flat)](http://twitter.com/brightdigit)
![GitHub](https://img.shields.io/github/license/brightdigit/ContributeWordPress)
![GitHub issues](https://img.shields.io/github/issues/brightdigit/ContributeWordPress)
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/brightdigit/ContributeWordPress/ContributeWordPress.yml?label=actions&logo=github&?branch=main)

[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fbrightdigit%2FContributeWordPress%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/brightdigit/ContributeWordPress)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fbrightdigit%2FContributeWordPress%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/brightdigit/ContributeWordPress)


[![Codecov](https://img.shields.io/codecov/c/github/brightdigit/ContributeWordPress)](https://codecov.io/gh/brightdigit/ContributeWordPress)
[![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/brightdigit/ContributeWordPress)](https://www.codefactor.io/repository/github/brightdigit/ContributeWordPress)
[![codebeat badge](https://codebeat.co/badges/54695d4b-98c8-4f0f-855e-215500163094)](https://codebeat.co/projects/github-com-brightdigit-ContributeWordPress-main)
[![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/brightdigit/ContributeWordPress)](https://codeclimate.com/github/brightdigit/ContributeWordPress)
[![Code Climate technical debt](https://img.shields.io/codeclimate/tech-debt/brightdigit/ContributeWordPress?label=debt)](https://codeclimate.com/github/brightdigit/ContributeWordPress)
[![Code Climate issues](https://img.shields.io/codeclimate/issues/brightdigit/ContributeWordPress)](https://codeclimate.com/github/brightdigit/ContributeWordPress)
[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)

# Table of Contents

* [Introduction](#introduction)
* [Requirements](#requirements)
* [Installation](#installation)
* [Usage](#usage)
* [Further Documentation](#further-documentation)
* [References](#references)
* [License](#license)

# Introduction

**ContributeWordPress** provides an pluggable easy abstract layer for accessing Keychain data as well as an API for encoding and decoding complex data in the Keychain.

## Requirements

**Apple Platforms**

- Xcode 14.3.1 or later
- Swift 5.8 or later
- iOS 14 / watchOS 7 / tvOS 14 / macOS 12 or later deployment targets

**Linux**

- Ubuntu 18.04 or later
- Swift 5.8 or later

## Installation

Use the Swift Package Manager to install this library via the repository url:

```
https://github.com/brightdigit/ContributeWordPress.git
```

Use version up to `1.0`.

# Usage

## Accessing the Keychain like a Database

**ContributeWordPress** supports the adding, updating, and querying for both generic and internet passwords. To do this you need to create a ``KeychainRepository`` to access the database to.

```
let repository = KeychainRepository(
defaultServiceName: "com.brightdigit.KeychainSyncDemo",
defaultServerName: "com.brightdigit.KeychainSyncDemo",
defaultAccessGroup: "MLT7M394S7.com.brightdigit.KeychainSyncDemo"
)
```

To call ``KeychainRepository.init(defaultServiceName:defaultServerName:defaultAccessGroup:defaultSynchronizable:logger:)`` you need to supply a the default ``InternetPasswordItem/server`` and ``GenericPasswordItem/service`` which is required by both types to query and create.

> You can also supply a `logger` to use for logging as well as an ``InternetPasswordItem.accessGroup`` for your ``InternetPasswordItem`` and ``GenericPasswordItem.accessGroup`` for your ``GenericPasswordItem``
To query, update, or add a new password, check out the documentation under ``StealthyRepository``.

## Using `StealthyModel` for Composite Objects

In many cases, you may want to use multiple items to store a single object such as the user's password with ``InternetPasswordItem`` as well as their token via ``GenericPasswordItem``. In this case, you'll want to use a ``StealthyModel``:

```swift
struct CompositeCredentials: StealthyModel {
typealias QueryBuilder = CompositeCredentialsQueryBuilder

internal init(userName: String, password: String?, token: String?) {
self.userName = userName
self.password = password
self.token = token
}

let userName: String

let password: String?

let token: String?
}
```

This is the perfect use case for ``StealthyModel`` and it only requires the implementation of a ``ModelQueryBuilder`` which defines how to build the queries for creating, updating, and deleting ``StealthyModel`` objects from the keychain:

* ``ModelQueryBuilder.updates(from:to:)`` require you to build an array of ``StealthyPropertyUpdate`` object which define the previous and new properties for the Keychain. Both the previous and new are optional in case you are only adding a new item as part of the update or only removing an old item.

* ``ModelQueryBuilder.properties(from:for:)`` is for creating a new model and requires the individual ``AnyStealthyProperty`` for each item to add to the keychain.

* ``ModelQueryBuilder.model(from:)`` builds the ``StealthyModel`` based on the ``AnyStealthyProperty`` items

* ``ModelQueryBuilder.queries(from:)`` builds a query dictionary depending the ``ModelQueryBuilder.QueryType`` passed. The keys to the query dictionary will be used by ``ModelQueryBuilder.model(from:)`` to define the keys of their resulting ``AnyStealthyProperty``. If there's only one object in your app, you can define ``ModelQueryBuilder.QueryType`` as `Void`:

```
static func queries(from _: Void) -> [String: Query] {
[
"password": TypeQuery(type: .internet),
"token": TypeQuery(type: .generic)
]
}
```

For more help, take a look at the [`Sample` projects located in the Swift Package.](https://github.com/brightdigit/ContributeWordPress/tree/main/Samples)

## Further Documentation

Further documentation is available at [the Swift Package Index.](https://swiftpackageindex.com/brightdigit/ContributeWordPress/1.0.0/documentation/ContributeWordPress)

# References

* [Using the Keychain to Manage User Secret](https://developer.apple.com/documentation/security/keychain_services/keychain_items/using_the_keychain_to_manage_user_secrets)

# License

This code is distributed under the MIT license. See the [LICENSE](https://github.com/brightdigit/ContributeWordPress/LICENSE) file for more info.
22 changes: 0 additions & 22 deletions Sources/ContributeWordPress/Array.swift

This file was deleted.

18 changes: 18 additions & 0 deletions Sources/ContributeWordPress/Decoder/BaseURLSite.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Foundation
import SyndiKit

/// A protocol representing a site with a base URL.
internal protocol BaseURLSite {
/// The main URL of the site.
var link: URL { get }

/// The base URL of the blog, if available.
var baseBlogURL: URL? { get }
}

extension BaseURLSite {
/// The base URL of the site.
public var baseURL: URL {
baseBlogURL ?? link
}
}
20 changes: 20 additions & 0 deletions Sources/ContributeWordPress/Decoder/SitesExportDecoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Foundation
import SyndiKit

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

/// A typealias that represents the section name for the blog
public typealias SectionName = String

/// A protocol for decoding WordPress sites from exports.
public protocol SitesExportDecoder {
/// Returns a dictionary of WordPress sites keyed by the filename of
/// the export file as section name.
///
/// - Parameter directoryURL: The URL of the directory containing the exports.
/// - Returns: A dictionary of WordPress sites keyed by section name.
/// - Throws: An error if sites couldn't be extracted from any of the export files.
func sites(fromExportsAt directoryURL: URL) throws -> [SectionName: WordPressSite]
}
81 changes: 81 additions & 0 deletions Sources/ContributeWordPress/Decoder/SitesExportSynDecoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import Contribute
import Foundation
import SyndiKit

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

/// A type that decodes WordPress export files using the `SynDecoder`.
public struct SitesExportSynDecoder: SitesExportDecoder {
/// The decoder used to decode the WordPress export file.
private let decoder: WordPressDecoder = SynDecoder()

/// Returns an array of URLs for all of the files in the given directory.
private let fileURLsFromDirectory: (URL) throws -> [URL]

/// Returns the last path component of the given URL without its extension.
/// This will be the section id for all posts found in the export file at given URL.
private let keyFromURL: (URL) -> String

public init(
fileURLsFromDirectory: @escaping (URL) throws -> [URL] =
Self.defaultFileURLs(atDirectory:),
keyFromURL: @escaping (URL) -> String = { $0.lastPathComponentWithoutExtension() }
) {
self.fileURLsFromDirectory = fileURLsFromDirectory
self.keyFromURL = keyFromURL
}

/// The method decodes `WordPressSite` from the given file URL.
///
/// - Parameters:
/// - url: The URL of export file containing the WordPress site with its posts.
/// - decoder: The WordPress decoder.
/// - Returns: The decoded WordPress site, or nil if decoding failed.
/// - Throws: An error of site couldn't be decoded from the given file..
private static func siteFromURL(
_ url: URL,
using decoder: WordPressDecoder
) throws -> WordPressSite? {
let data = try Data(contentsOf: url)
return try decoder.decodeSites(fromData: data, allowInvalidCharacters: true)
}

/// Returns a dictionary of WordPress sites keyed by filename.
///
/// - Parameter directoryURL: The URL of the directory containing the exports.
/// - Returns: A dictionary of WordPress sites keyed by section name.
/// - Throws: An error if sites couldn't be extracted from any of the export files.
public func sites(
fromExportsAt directoryURL: URL
) throws -> [SectionName: WordPressSite] {
let files = try fileURLsFromDirectory(directoryURL)

let feedPairs = try files.map { url -> (String, WordPressSite?) in
try (self.keyFromURL(url), Self.siteFromURL(url, using: decoder))
}

return Dictionary(uniqueKeysWithValues: feedPairs).compactMapValues { $0 }
}
}

extension SitesExportSynDecoder {
/// A default logic for finding all files found at given directory.
///
/// - Parameter directoryURL: The directory URL.
/// - Returns: An array of URLs for export files found in the given directory.
public static func defaultFileURLs(atDirectory directoryURL: URL) -> [URL] {
let enumerator = FileManager.default.enumerator(
at: directoryURL,
includingPropertiesForKeys: nil
)

guard let enumerator = enumerator else {
assertionFailure("\(directoryURL) returned empty enumerator.")
return []
}

return enumerator.compactMap { $0 as? URL }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ import SyndiKit
import FoundationNetworking
#endif

/// An extension that enables SynDecoder to decode WordPress posts.
/// An extension that enables SynDecoder to decode WordPress sites.
extension SynDecoder: WordPressDecoder {
/// Decodes an array of WordPress posts from the given data.
/// Decodes an array of WordPress sites from the given data.
///
/// - Parameters:
/// - data: The data to decode.
/// - allowInvalidCharacters: Whether to allow invalid characters in decoded data.
/// - Returns: An array of WordPress posts, or nil if decoding failed.
/// - allowInvalidCharacters: Whether to allow invalid characters in the data.
/// - Returns: The decoded WordPress site, or nil if decoding failed.
/// - Throws: An error if data couldn't be decoded.
public func decodePosts(
public func decodeSites(
fromData data: Data,
allowInvalidCharacters: Bool
) throws -> [WordPressPost]? {
) throws -> WordPressSite? {
let text = String(bytes: data, encoding: .utf8)?
.replacingOccurrences(of: "\u{10}", with: "")
.data(using: .utf8, allowLossyConversion: true)
Expand All @@ -31,6 +31,6 @@ extension SynDecoder: WordPressDecoder {

let feed = try decode(newData)
let rss = feed as? RSSFeed
return rss?.channel.items.compactMap(\.wpPost)
return try rss.map(\.channel).map(WordPressSite.init)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import SyndiKit
import FoundationNetworking
#endif

/// A protocol for decoding WordPress posts from raw data.
/// A protocol for decoding WordPress sites from raw data.
public protocol WordPressDecoder {
/// Decodes an array of WordPress posts from the given data.
/// Decodes an array of WordPress sites from the given data.
///
/// - Parameters:
/// - data: The data to decode.
/// - allowInvalidCharacters: Whether to allow invalid characters in the data.
/// - Returns: An array of WordPress posts, or nil if decoding failed.
/// - Returns: An array of WordPress sites, or nil if decoding failed.
/// - Throws: An error if data couldn't be decoded.
func decodePosts(
func decodeSites(
fromData data: Data,
allowInvalidCharacters: Bool
) throws -> [WordPressPost]?
) throws -> WordPressSite?
}
Loading

0 comments on commit 36ffc69

Please sign in to comment.