Skip to content

Commit

Permalink
Merge pull request #3 from twof/ComputerReadableOutput
Browse files Browse the repository at this point in the history
Create computer readable output for ingestion by CI
  • Loading branch information
twof committed Oct 25, 2020
2 parents f0b1121 + f08275d commit 362313c
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 54 deletions.
19 changes: 14 additions & 5 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,19 @@ jobs:
uses: trilom/file-changes-action@v1.2.3
with:
output: ' '
- uses: pre-commit/action@v2.0.0
- id: pre_commit_action
uses: pre-commit/action@v2.0.0
with:
extra_args: downstream --files ${{ steps.file_changes.outputs.files}}
- uses: JoseThen/comment-pr@v1
with:
comment: 'This PR is the bees knees!'
extra_args: downstream --files ${{ steps.file_changes.outputs.files }};
- id: get_changes
run: |
content="$(swift run downstream -o human ${{ steps.file_changes.outputs.files }})"
content="${content//'%'/'%25'}"
content="${content//$'\n'/'%0A'}"
content="${content//$'\r'/'%0D'}"
echo "::set-output name=test::$content"
- uses: daohoangson/comment-on-github@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
body: ${{ steps.get_changes.outputs.test }}
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
repos:
- repo: https://github.com/twof/Downstream
rev: 0.0.6
hooks:
- id: downstream
- repo: .
rev: HEAD
hooks:
- id: downstream
9 changes: 9 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@
"version": "4.1.1"
}
},
{
"package": "swift-argument-parser",
"repositoryURL": "https://github.com/apple/swift-argument-parser",
"state": {
"branch": null,
"revision": "92646c0cdbaca076c8d3d0207891785b3379cbff",
"version": "0.3.1"
}
},
{
"package": "Yams",
"repositoryURL": "https://github.com/jpsim/Yams.git",
Expand Down
13 changes: 11 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,23 @@ let package = Package(
name: "downstream",
dependencies: [
.package(url: "https://github.com/jpsim/Yams.git", from: "4.0.0"),
.package(url: "https://github.com/JohnSundell/Files", from: "4.0.0")
.package(url: "https://github.com/JohnSundell/Files", from: "4.0.0"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "0.3.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "downstream",
dependencies: ["Yams", "Files"]),
dependencies: [
"Yams",
"Files",
.product(name: "ArgumentParser", package: "swift-argument-parser")
],
exclude: [
"downstream.yml"
]
),
.testTarget(
name: "DownstreamTests",
dependencies: ["downstream"]),
Expand Down
71 changes: 68 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Downstream

A pre-commit hook to alert users when files they're changing may cause docs to be out of date. Downstream is more or less a reverse dependency manager in that it's used to describe what relies on your code rather than what your code relies on.
A tool to alert users when files they're changing may cause docs to be out of date. Downstream is more or less a reverse dependency manager in that it's used to describe what relies on your code rather than what your code relies on.

## Why?

Expand All @@ -15,11 +15,58 @@ In your `.pre-commit-config.yaml` add the following
```yaml
repos:
- repo: https://github.com/twof/Downstream
rev: 0.0.3
rev: 0.0.7
hooks:
- id: downstream
```

### Github Actions Installation

You can also have Github comment on PRs when file changes may necessitate other changes. Here is an example setup.

```yaml
name: pre-commit

on:
workflow_dispatch:
pull_request:
push:
branches: [master]

jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- id: file_changes
uses: trilom/file-changes-action@v1.2.3
with:
output: ' '
- id: get_changes
run: |
content="$(swift run downstream -o human ${{ steps.file_changes.outputs.files }})"
content="${content//'%'/'%25'}"
content="${content//$'\n'/'%0A'}"
content="${content//$'\r'/'%0D'}"
echo "::set-output name=test::$content"
- uses: daohoangson/comment-on-github@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
body: ${{ steps.get_changes.outputs.test }}
```

For some background, `file_changes` records a list of files that have been changed in this PR to `steps.file_changes.outputs.files`. It's basically the equivalent of `git diff --name-only`.

These lines
```
content="${content//'%'/'%25'}"
content="${content//$'\n'/'%0A'}"
content="${content//$'\r'/'%0D'}"
```
are necessary due to (a bug in Github Actions)[https://github.community/t/set-output-truncates-multiline-strings/16852] that prevents multiple lines from being passed to `set-output`.

### Project Structure

You will need to put a file called `downstream.yml` in the directory with the file you'd like to attach documentation to.
Expand Down Expand Up @@ -48,8 +95,26 @@ Downstream...............................................................Passed
- hook id: downstream
- duration: 1.19s
Our records indicate that you may need to update the docs at https://github.com/twof/Downstream/edit/main/README.md because changes were made to Associations.swift
Due to changes made to Sources/downstream/main.swift, you may need to make updates to the following:
https://github.com/twof/Downstream/blob/main/README.md
[main b5db130] bumped pre-commit hook
2 files changed, 1 insertion(+), 2 deletions(-)
```

### Usage

Beyond its usage as a pre-commit hook, Downstream can also be executed manually for integration with CI and whatnot like can be seen above with Github Actions. It can currently produce output based on the format requested by the user with the `-o` flag. Possible options are `human` for human friendly output like seen in the example above, `yaml`, `json`, and `list` which simply lists out all of the docs that may need updates in a format that's convenient for intake in a bash script.

```
$ downstream -h
USAGE: downstream-argument [--output-format <output-format>] [<files> ...]
ARGUMENTS:
<files> Input files
OPTIONS:
-o, --output-format <output-format>
The format of the output
-h, --help Show help information.
```
109 changes: 109 additions & 0 deletions Sources/downstream/DownstreamArgument.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// File.swift
//
//
// Created by Alex Reilly on 10/24/20.
//

import ArgumentParser
import Foundation
import Files
import Yams

enum OutputFormat: String, ExpressibleByArgument {
case json
case yaml
case list
case humanFriendly = "human"
}

struct DownstreamArgument: ParsableCommand {
@Option(name: .shortAndLong, help: "The format of the output")
var outputFormat: OutputFormat?

@Argument(help: "Input files")
var files: [String]

mutating func run() throws {
let todoList = todos(fileList: self.files)
let output = outputFactory(todos: todoList, format: outputFormat)

print(output)
}

func todos(fileList: [String]) -> [String: [String]] {
let decoder = YAMLDecoder()

return fileList.reduce(into: [String: [String]]()) { (result, filePath) in
let changedFile = try! File(path: filePath)

if
let parent = changedFile.parent,
let downsteamYML = try? parent.file(named: "downstream.yml").read()
{
guard let associationsFile = try? decoder.decode(AssociationsFile.self, from: downsteamYML) else {
print("\(parent.path)downstream.yml could not be parsed")
DownstreamArgument.exit()
}
let fileName = changedFile.name
let newTodos = associationsFile.associations[fileName]
result[filePath] = newTodos
}
}
}

/// Formats the todo list in the selected format type. Defaults to .humanFriendly if no format type is provided.
/// - Parameters:
/// - todos: Todo list in the form of changed file -> associated tasks
/// - format: How the list ought to be formatted. Defaults to .humanFriendly if no format type is provided
/// - Returns: Todo list formatted as desired.
func outputFactory(todos: [String: [String]], format: OutputFormat?) -> String {
let format = format ?? .humanFriendly

switch format {
case .humanFriendly:
return humanReadableOutput(todos: todos)
case .json:
return jsonOutput(todos: todos)
case .yaml:
return yamlOutput(todos: todos)
case .list:
return listOutput(todos: todos)
}
}

func humanReadableOutput(todos: [String: [String]]) -> String {
return todos.map { (filePath, todos) in
return "Due to changes made to \(filePath), you may need to make updates to the following: \n \(todos.joined(separator: "\n"))"
}.joined(separator: "\n")
}

func jsonOutput(todos: [String: [String]]) -> String {
let encoder = JSONEncoder()
guard
let jsonData = try? encoder.encode(todos),
let jsonString = String(data: jsonData, encoding: .utf8)
else {
print("Todo list could not be encoded to JSON string. \n List: \(todos)")
Self.exit()
}

return jsonString
}

func yamlOutput(todos: [String: [String]]) -> String {
let encoder = YAMLEncoder()
guard
let yamlString = try? encoder.encode(todos)
else {
print("Todo list could not be encoded to YAML string. \n List: \(todos)")
Self.exit()
}

return yamlString
}

func listOutput(todos: [String: [String]]) -> String {
return Set(todos.values).map { $0.joined(separator: "\n") }.joined(separator: "\n")
}
}
2 changes: 2 additions & 0 deletions Sources/downstream/downstream.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ associations:
- https://github.com/twof/Downstream/blob/main/README.md
Associations.swift:
- https://github.com/twof/Downstream/blob/main/README.md
DownstreamArgument.swift:
- https://github.com/twof/Downstream/blob/main/README.md
41 changes: 1 addition & 40 deletions Sources/downstream/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,7 @@
//
// Created by Alex Reilly on 10/22/20.
//

import Yams
import Files
import Foundation

let decoder = YAMLDecoder()

let fileList = CommandLine.arguments[1...]
let todos = fileList.flatMap { filePath -> [String] in
let changedFile = try! File(path: filePath)

// print("filePath", filePath)
// print("parent", changedFile.parent?.path)
// print("yaml", try? changedFile.parent?.file(named: "downstream.yml").read())
// let associationsData = (try? changedFile.parent?.file(named: "downstream.yml").read() as? Data).flatMap { try? decoder.decode(AssociationsFile.self, from: $0) }
// print("associations", associationsData)


if
let parent = changedFile.parent,
let downsteamYML = try? parent.file(named: "downstream.yml").read()
{
guard let associationsFile = try? decoder.decode(AssociationsFile.self, from: downsteamYML) else {
print("\(parent.path)downstream.yml could not be parsed")
exit(1)
}
let fileName = changedFile.name
let newTodos = associationsFile.associations[fileName] ?? []
return newTodos.map {
"Our records indicate that you may need to update the docs at \($0) because changes were made to \(fileName)"
}
}

return []
}

if !todos.isEmpty {
todos.forEach {
print($0)
}
}

DownstreamArgument.main()
exit(0)

0 comments on commit 362313c

Please sign in to comment.