Skip to content

Commit 9cafcd9

Browse files
authored
Build MVP website from README (#338)
#### Summary Adds the ability to convert the README.md to a website. This productionizes #336 following positive internal feedback on the direction. The next steps will be to set up a GitHub workflow to publish the site to GitHub Pages. We may also want to continue to polish the style. #### Reasoning A website will make the style guide more accessible. We chose to generate the website from the README so that the existing workflow for updating the style guide remains the same. #### Testing - [x] Run `bundle exec rake site:serve` locally and check the content at http://localhost:4000 - [x] Verify that top badges and SPM command plugin sections are filtered out of the site | Top | Code block | Footer | | --- | --- | --- | |![image](https://github.com/user-attachments/assets/176cd61e-fb77-40ef-aa5c-8d25858eb883) | ![image](https://github.com/user-attachments/assets/1c080b2a-2edd-4d7a-a674-ab1983cb61d4) | ![image](https://github.com/user-attachments/assets/0f8bc71e-04ae-4719-97e3-7595635f6a18) | #### Please review @airbnb/swift-styleguide-maintainers
1 parent 9f5bc5c commit 9cafcd9

File tree

12 files changed

+298
-12
lines changed

12 files changed

+298
-12
lines changed

.github/workflows/main.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,16 @@ jobs:
4040
xcode: ${{ matrix.xcode }}
4141
- name: Run Unit Tests
4242
run: swift test
43+
44+
site-build:
45+
name: Site Build
46+
runs-on: macos-15
47+
steps:
48+
- uses: actions/checkout@v6
49+
- uses: ruby/setup-ruby@v1
50+
with:
51+
bundler-cache: true
52+
- name: Build site
53+
run: bundle exec rake site:build
54+
env:
55+
JEKYLL_ENV: production

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
.build
22
.swiftpm
33
.DS_Store
4-
.vscode
4+
.vscode
5+
6+
# The site generated by Jekyll.
7+
_site

Gemfile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
source 'https://rubygems.org' do
2-
gem "rake", "~> 13.0.0"
3-
end
1+
source 'https://rubygems.org'
2+
3+
gem "rake", "~> 13.0.0"
4+
gem "jekyll", "~> 4.4.1"

Gemfile.lock

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,89 @@
1-
GEM
2-
specs:
3-
41
GEM
52
remote: https://rubygems.org/
63
specs:
4+
addressable (2.8.7)
5+
public_suffix (>= 2.0.2, < 7.0)
6+
base64 (0.3.0)
7+
bigdecimal (3.3.1)
8+
colorator (1.1.0)
9+
concurrent-ruby (1.3.5)
10+
csv (3.3.5)
11+
em-websocket (0.5.3)
12+
eventmachine (>= 0.12.9)
13+
http_parser.rb (~> 0)
14+
eventmachine (1.2.7)
15+
ffi (1.17.2)
16+
ffi (1.17.2-arm64-darwin)
17+
forwardable-extended (2.6.0)
18+
google-protobuf (4.33.1)
19+
bigdecimal
20+
rake (>= 13)
21+
google-protobuf (4.33.1-arm64-darwin)
22+
bigdecimal
23+
rake (>= 13)
24+
http_parser.rb (0.8.0)
25+
i18n (1.14.7)
26+
concurrent-ruby (~> 1.0)
27+
jekyll (4.4.1)
28+
addressable (~> 2.4)
29+
base64 (~> 0.2)
30+
colorator (~> 1.0)
31+
csv (~> 3.0)
32+
em-websocket (~> 0.5)
33+
i18n (~> 1.0)
34+
jekyll-sass-converter (>= 2.0, < 4.0)
35+
jekyll-watch (~> 2.0)
36+
json (~> 2.6)
37+
kramdown (~> 2.3, >= 2.3.1)
38+
kramdown-parser-gfm (~> 1.0)
39+
liquid (~> 4.0)
40+
mercenary (~> 0.3, >= 0.3.6)
41+
pathutil (~> 0.9)
42+
rouge (>= 3.0, < 5.0)
43+
safe_yaml (~> 1.0)
44+
terminal-table (>= 1.8, < 4.0)
45+
webrick (~> 1.7)
46+
jekyll-sass-converter (3.1.0)
47+
sass-embedded (~> 1.75)
48+
jekyll-watch (2.2.1)
49+
listen (~> 3.0)
50+
json (2.16.0)
51+
kramdown (2.5.1)
52+
rexml (>= 3.3.9)
53+
kramdown-parser-gfm (1.1.0)
54+
kramdown (~> 2.0)
55+
liquid (4.0.4)
56+
listen (3.9.0)
57+
rb-fsevent (~> 0.10, >= 0.10.3)
58+
rb-inotify (~> 0.9, >= 0.9.10)
59+
mercenary (0.4.0)
60+
pathutil (0.16.2)
61+
forwardable-extended (~> 2.6)
62+
public_suffix (6.0.2)
763
rake (13.0.6)
64+
rb-fsevent (0.11.2)
65+
rb-inotify (0.11.1)
66+
ffi (~> 1.0)
67+
rexml (3.4.4)
68+
rouge (4.6.1)
69+
safe_yaml (1.0.5)
70+
sass-embedded (1.94.2)
71+
google-protobuf (~> 4.31)
72+
rake (>= 13)
73+
sass-embedded (1.94.2-arm64-darwin)
74+
google-protobuf (~> 4.31)
75+
terminal-table (3.0.2)
76+
unicode-display_width (>= 1.1.1, < 3)
77+
unicode-display_width (2.6.0)
78+
webrick (1.9.1)
879

980
PLATFORMS
1081
arm64-darwin-23
1182
ruby
1283

1384
DEPENDENCIES
14-
rake (~> 13.0.0)!
85+
jekyll (~> 4.4.1)
86+
rake (~> 13.0.0)
1587

1688
BUNDLED WITH
1789
2.5.4

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,6 +1056,8 @@ _You can enable the following settings in Xcode by running [this script](resourc
10561056
]
10571057
```
10581058

1059+
</details>
1060+
10591061
* <a id='long-typealias'></a>(<a href='#long-typealias'>link</a>) [Long](https://github.com/airbnb/swift#column-width) type aliases of protocol compositions should wrap before the `=` and before each individual `&`. [![SwiftFormat: wrapArguments](https://img.shields.io/badge/SwiftFormat-wrapArguments-7B0051.svg)](https://github.com/nicklockwood/SwiftFormat/blob/main/Rules.md#wrapArguments)
10601062

10611063
<details>
@@ -1084,6 +1086,8 @@ _You can enable the following settings in Xcode by running [this script](resourc
10841086
& UniverseSimulatorServiceProviding
10851087
```
10861088

1089+
</details>
1090+
10871091
* <a id='sort-typealiases'></a>(<a href='#sort-typealiases'>link</a>) **Sort protocol composition type aliases alphabetically.** [![SwiftFormat: sortTypealiases](https://img.shields.io/badge/SwiftFormat-sortTypealiases-7B0051.svg)](https://github.com/nicklockwood/SwiftFormat/blob/main/Rules.md#sortTypealiases)
10881092

10891093
<details>
@@ -1110,6 +1114,8 @@ _You can enable the following settings in Xcode by running [this script](resourc
11101114
& UniverseSimulatorServiceProviding
11111115
```
11121116

1117+
</details>
1118+
11131119
* <a id='prefer-if-let-shorthand'></a>(<a href='#prefer-if-let-shorthand'>link</a>) Omit the right-hand side of the expression when unwrapping an optional property to a non-optional property with the same name. [![SwiftFormat: redundantOptionalBinding](https://img.shields.io/badge/SwiftFormat-redundantOptionalBinding-7B0051.svg)](https://github.com/nicklockwood/SwiftFormat/blob/main/Rules.md#redundantOptionalBinding)
11141120

11151121
<details>
@@ -1142,6 +1148,8 @@ _You can enable the following settings in Xcode by running [this script](resourc
11421148
else { … }
11431149
```
11441150

1151+
</details>
1152+
11451153
* <a id='else-on-same-line'></a>(<a href='#else-on-same-line'>link</a>) **Else statements should start on the same line as the previous condition's closing brace, unless the conditions are separated by a blank line or comments.** [![SwiftFormat: elseOnSameLine](https://img.shields.io/badge/SwiftFormat-elseOnSameLine-7B0051.svg)](https://github.com/nicklockwood/SwiftFormat/blob/main/Rules.md#elseOnSameLine)
11461154

11471155
<details>
@@ -1196,6 +1204,8 @@ _You can enable the following settings in Xcode by running [this script](resourc
11961204
}
11971205
```
11981206

1207+
</details>
1208+
11991209
* <a id='multi-line-conditions'></a>(<a href='#multi-line-conditions'>link</a>) **Multi-line conditional statements should break after the leading keyword.** Indent each individual statement by [2 spaces](https://github.com/airbnb/swift#spaces-over-tabs). [![SwiftFormat: wrapArguments](https://img.shields.io/badge/SwiftFormat-wrapArguments-7B0051.svg)](https://github.com/nicklockwood/SwiftFormat/blob/main/Rules.md#wrapArguments)
12001210

12011211
<details>
@@ -1609,6 +1619,8 @@ _You can enable the following settings in Xcode by running [this script](resourc
16091619
}
16101620
```
16111621

1622+
</details>
1623+
16121624
* <a id='indent-multiline-string-literals'></a>(<a href='#indent-multiline-string-literals'>link</a>) **Indent the body and closing triple-quote of multiline string literals**, unless the string literal begins on its own line in which case the string literal contents and closing triple-quote should have the same indentation as the opening triple-quote. [![SwiftFormat: indent](https://img.shields.io/badge/SwiftFormat-indent-7B0051.svg)](https://github.com/nicklockwood/SwiftFormat/blob/main/Rules.md#indent)
16131625

16141626
<details>
@@ -1779,6 +1791,8 @@ _You can enable the following settings in Xcode by running [this script](resourc
17791791
}
17801792
```
17811793

1794+
</details>
1795+
17821796
* <a id='no-spaces-around-function-parens'></a>(<a href='#no-spaces-around-parens'>link</a>) For function calls and declarations, there should be no spaces before or inside the parentheses of the argument list. [![SwiftFormat: spaceInsideParens](https://img.shields.io/badge/SwiftFormat-spaceInsideParens-7B0051.svg)](https://github.com/nicklockwood/SwiftFormat/blob/main/Rules.md#spaceInsideParens) [![SwiftFormat: spaceAroundParens](https://img.shields.io/badge/SwiftFormat-spaceAroundParens-7B0051.svg)](https://github.com/nicklockwood/SwiftFormat/blob/main/Rules.md#spaceAroundParens)
17831797

17841798
<details>
@@ -3010,6 +3024,8 @@ _You can enable the following settings in Xcode by running [this script](resourc
30103024
}
30113025
```
30123026

3027+
</details>
3028+
30133029
* <a id='avoid-global-functions'></a>(<a href='#avoid-global-functions'>link</a>) **Avoid global functions whenever possible.** Prefer methods within type definitions.
30143030

30153031
<details>
@@ -4694,6 +4710,8 @@ _You can enable the following settings in Xcode by running [this script](resourc
46944710
}
46954711
```
46964712

4713+
</details>
4714+
46974715
* <a id='prefer-throwing-tests'></a>(<a href='#prefer-throwing-tests'>link</a>) **Prefer throwing tests to `try!`**. `try!` will crash your test suite like a force-unwrapped optional. XCTest and Swift Testing support throwing test methods, so use that instead. [![SwiftFormat: noForceTryInTests](https://img.shields.io/badge/SwiftFormat-noForceTryInTests-7B0051.svg)](https://github.com/nicklockwood/SwiftFormat/blob/main/Rules.md#noForceTryInTests)
46984716

46994717
<details>

Rakefile

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,26 +30,55 @@ namespace :update do
3030
temp_dir = Dir.mktmpdir
3131
artifact_bundle_url = "https://github.com/calda/SwiftFormat-nightly/releases/download/#{latest_version_number}/swiftformat.artifactbundle.zip"
3232
artifact_bundle_zip_path = "#{temp_dir}/swiftformat.artifactbundle.zip"
33-
33+
3434
sh "curl #{artifact_bundle_url} -L --output #{artifact_bundle_zip_path}"
3535
checksum = `swift package compute-checksum #{artifact_bundle_zip_path}`
3636

3737
# Update the Package.swift file to reference this version
3838
package_manifest_path = 'Package.swift'
3939
package_manifest_content = File.read(package_manifest_path)
40-
40+
4141
updated_swift_format_reference = <<-EOS
4242
.binaryTarget(
4343
name: "swiftformat",
4444
url: "https://github.com/calda/SwiftFormat-nightly/releases/download/#{latest_version_number}/SwiftFormat.artifactbundle.zip",
4545
checksum: "#{checksum.strip}"
4646
),
4747
EOS
48-
48+
4949
regex = /[ ]*.binaryTarget\([\S\s]*name: "swiftformat"[\S\s]*?\),\s/
5050
updated_package_manifest = package_manifest_content.gsub(regex, updated_swift_format_reference)
5151
File.open(package_manifest_path, "w") { |file| file.puts updated_package_manifest }
52-
52+
5353
puts "Updated Package.swift to reference SwiftFormat #{latest_version_number}"
5454
end
5555
end
56+
57+
namespace :site do
58+
desc 'Prints the README content used to build the site'
59+
task :filter_readme do
60+
require_relative 'site/site_content'
61+
puts SiteContent.new.filter_readme
62+
end
63+
64+
desc 'Prepares index.md and syntax highlighting assets'
65+
task :prepare do
66+
require_relative 'site/site_content'
67+
puts '📋 Generating index.md from README.md with frontmatter...'
68+
SiteContent.new.write_index
69+
puts '🎨 Generating syntax highlighting CSS...'
70+
SiteContent.new.generate_syntax_css
71+
end
72+
73+
desc 'Builds the static site into _site/'
74+
task build: :prepare do
75+
env = { 'JEKYLL_ENV' => ENV.fetch('JEKYLL_ENV', 'production') }
76+
sh env, 'bundle exec jekyll build --source site/src'
77+
end
78+
79+
desc 'Serves the site to support previewing its content during development'
80+
task serve: :prepare do
81+
env = { 'JEKYLL_ENV' => 'development' }
82+
sh env, 'bundle exec jekyll serve --source site/src'
83+
end
84+
end

site/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# These site source files are generated.
2+
src/index.md
3+
src/assets/css/syntax.css

site/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Website
2+
3+
## Local development
4+
5+
To start the local development server, run the following command:
6+
7+
```bash
8+
bundle exec rake site:serve
9+
```
10+
11+
Once the server is running, open [http://localhost:4000](http://localhost:4000) in your browser to preview the site.

site/site_content.rb

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# frozen_string_literal: true
2+
3+
require 'open3'
4+
5+
class SiteContent
6+
attr_reader :readme_path, :index_path, :syntax_css_path
7+
8+
def initialize()
9+
site_dir = File.expand_path('src', __dir__)
10+
@readme_path = File.expand_path('../README.md', __dir__)
11+
@index_path = File.join(site_dir, 'index.md')
12+
@syntax_css_path = File.join(site_dir, 'assets/css/syntax.css')
13+
end
14+
15+
def filter_readme
16+
(filter_readme_lines + ['']).join("\n")
17+
end
18+
19+
# Write index.md file.
20+
def write_index
21+
File.write(index_path, generate_front_matter + filter_readme)
22+
end
23+
24+
# Write syntax.css file.
25+
def generate_syntax_css
26+
stdout, stderr, status = Open3.capture3('bundle', 'exec', 'rougify', 'style', 'github.light')
27+
raise "rougify failed:\n#{stderr}" unless status.success?
28+
29+
File.write(syntax_css_path, stdout)
30+
end
31+
32+
private
33+
34+
def generate_front_matter
35+
<<~FRONT
36+
---
37+
layout: default
38+
---
39+
40+
FRONT
41+
end
42+
43+
def filter_readme_lines
44+
lines = File.readlines(readme_path, chomp: true)
45+
filtered = []
46+
skip_plugin_section = false
47+
skip_amendments_section = false
48+
49+
lines.each do |line|
50+
# Exclude the SPM command plugin section from the site.
51+
if line.start_with?('## Swift Package Manager command plugin')
52+
skip_plugin_section = true
53+
next
54+
elsif skip_plugin_section
55+
skip_plugin_section = false if line.start_with?('## ')
56+
next if skip_plugin_section
57+
end
58+
59+
if line.start_with?('## Amendments')
60+
skip_amendments_section = true
61+
next
62+
elsif skip_amendments_section
63+
skip_amendments_section = false if line.start_with?('** ')
64+
next if skip_amendments_section
65+
end
66+
67+
# Exclude the badges from the site.
68+
stripped = line.strip
69+
next if stripped.start_with?('[![](') && stripped.include?('swiftpackageindex.com')
70+
71+
filtered << line.rstrip
72+
end
73+
74+
filtered
75+
end
76+
end

site/src/_config.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
title: Airbnb Swift Style Guide
2+
github_url: https://github.com/airbnb/swift
3+
markdown: kramdown
4+
kramdown:
5+
input: GFM
6+
parse_block_html: true

0 commit comments

Comments
 (0)