daifuku 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1fdf491fb8af78cfb95b601dd4f4fad28a1a4c73003498de29b929206199beb6
4
+ data.tar.gz: 9efe45238a4ea0ea0ae5d06b88b440f84044c520af425b7e35e5e8f653bbcf10
5
+ SHA512:
6
+ metadata.gz: fcaa3cb1c984235e7e383d63896352966ee649bb858b4865faa1c0598335439d2467c8993bd50dcbb07ef69035aa6b21c6d0f7c4cfd0ca8c4f0172dff325af73
7
+ data.tar.gz: 8de8c756e0264b0cb353b14115fd93a0d7cf3ac787e41eec267c9fb375cd80def016423de3145be69216268738422c95132d71ed97a383a654c2103e5d4a6275
@@ -0,0 +1,31 @@
1
+ name: Ruby
2
+
3
+ on:
4
+ push:
5
+ branches: [ "main" ]
6
+ pull_request:
7
+ branches: [ "main" ]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ test:
14
+
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ matrix:
18
+ ruby-version: ['3.1', head]
19
+
20
+ steps:
21
+ - uses: actions/checkout@v3
22
+ - name: Set up Ruby
23
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
24
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
25
+ # uses: ruby/setup-ruby@v1
26
+ uses: ruby/setup-ruby@55283cc23133118229fd3f97f9336ee23a179fcf # v1.146.0
27
+ with:
28
+ ruby-version: ${{ matrix.ruby-version }}
29
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
30
+ - name: Run tests
31
+ run: bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,56 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ # Ignore Byebug command history file.
17
+ .byebug_history
18
+
19
+ ## Specific to RubyMotion:
20
+ .dat*
21
+ .repl_history
22
+ build/
23
+ *.bridgesupport
24
+ build-iPhoneOS/
25
+ build-iPhoneSimulator/
26
+
27
+ ## Specific to RubyMotion (use of CocoaPods):
28
+ #
29
+ # We recommend against adding the Pods directory to your .gitignore. However
30
+ # you should judge for yourself, the pros and cons are mentioned at:
31
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
32
+ #
33
+ # vendor/Pods/
34
+
35
+ ## Documentation cache and generated files:
36
+ /.yardoc/
37
+ /_yardoc/
38
+ /doc/
39
+ /rdoc/
40
+
41
+ ## Environment normalization:
42
+ /.bundle/
43
+ /vendor/bundle
44
+ /lib/bundler/man/
45
+
46
+ # for a library or gem, you might want to ignore these files since the code is
47
+ # intended to run in multiple environments; otherwise, check them in:
48
+ # Gemfile.lock
49
+ # .ruby-version
50
+ # .ruby-gemset
51
+
52
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
53
+ .rvmrc
54
+
55
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
+ # .rubocop-https?--*
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.2
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+ gem 'pry'
5
+ gem 'activesupport-inflector', '~> 0.1.0'
6
+ gem 'i18n'
data/Gemfile.lock ADDED
@@ -0,0 +1,55 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ daifuku (0.9.0)
5
+ nokogiri
6
+ redcarpet
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport-inflector (0.1.0)
12
+ coderay (1.1.3)
13
+ concurrent-ruby (1.2.2)
14
+ diff-lcs (1.5.0)
15
+ i18n (1.14.1)
16
+ concurrent-ruby (~> 1.0)
17
+ method_source (1.0.0)
18
+ mini_portile2 (2.8.4)
19
+ nokogiri (1.15.3)
20
+ mini_portile2 (~> 2.8.2)
21
+ racc (~> 1.4)
22
+ pry (0.14.2)
23
+ coderay (~> 1.1)
24
+ method_source (~> 1.0)
25
+ racc (1.7.1)
26
+ rake (13.0.6)
27
+ redcarpet (3.6.0)
28
+ rspec (3.12.0)
29
+ rspec-core (~> 3.12.0)
30
+ rspec-expectations (~> 3.12.0)
31
+ rspec-mocks (~> 3.12.0)
32
+ rspec-core (3.12.2)
33
+ rspec-support (~> 3.12.0)
34
+ rspec-expectations (3.12.3)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.12.0)
37
+ rspec-mocks (3.12.6)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.12.0)
40
+ rspec-support (3.12.1)
41
+
42
+ PLATFORMS
43
+ ruby
44
+
45
+ DEPENDENCIES
46
+ activesupport-inflector (~> 0.1.0)
47
+ bundler (~> 2.0)
48
+ daifuku!
49
+ i18n
50
+ pry
51
+ rake (~> 13.0)
52
+ rspec (~> 3.0)
53
+
54
+ BUNDLED WITH
55
+ 2.4.14
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Cookpad Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # daifuku
2
+ A markdown parser and compiler for log definitions in mobile applications
3
+
4
+ ## Usage
5
+ Automatic generation of document-based type-safe log implementation code
6
+
7
+ ![Transpile](docs/images/transpile.png)
8
+
9
+ ### Step 1
10
+ Consider log specifications and write documentation.
11
+
12
+ - Syntax: [docs/syntax.ja.md](docs/syntax.ja.md)
13
+ - Example: [example/LogDefinitions/recipe_search.md](./example/LogDefinitions/recipe_search.md)
14
+
15
+ #### Log definition documents (Markdown)
16
+
17
+ ```md
18
+ # recipe_search
19
+
20
+ An category of events related to recipe search.
21
+
22
+ ## search
23
+
24
+ An event sent when users perform a recipe search.
25
+
26
+ - keyword: !string 256
27
+ - Search keyword
28
+ - order: SearchOrder
29
+ - latest, popularity
30
+
31
+ ## show_recipe
32
+
33
+ An event sent when users move from the search results screen to the recipe details screen.
34
+
35
+ - recipe_id: !integer
36
+ - ID of the selected recipe
37
+
38
+ ```
39
+
40
+ ### Step 2
41
+
42
+ Run the script.
43
+
44
+ - Example: [example/iOS/generate-log-classes.rb](./example/iOS/generate-log-classes.rb)
45
+
46
+ ```sh
47
+ bundle install
48
+ ruby example/iOS/generate-log-classes.rb
49
+ ```
50
+
51
+ After that, the following `.swift` files will be automatically generated.
52
+
53
+ ```sh
54
+ ls example/AutoGenerated
55
+ # CommonPayload.swift LogCategories.swift
56
+ ```
57
+
58
+ #### Log definition code (Swift)
59
+
60
+ ```swift
61
+ /// Events on the recipe search screen
62
+ public enum RecipeSearch: LogCategory {
63
+ /// An event sent when users perform a recipe search.
64
+ case search(keyword: String, order: SearchOrder)
65
+ /// An event sent when users move from the search results screen to the recipe details screen.
66
+ case showRecipe(recipeId: Int64)
67
+ }
68
+ ```
69
+
70
+ ### Step 3
71
+
72
+ Send logs with the enums of the log definition.
73
+
74
+ #### Log implementation code (Swift)
75
+
76
+ ```swift
77
+ // After the search request
78
+ logger.post(
79
+ RecipeSearch.search(
80
+ keyword: keyword, // “egg"
81
+ order: order // .latest
82
+ )
83
+ )
84
+ ```
85
+
86
+ ## What makes you happy?
87
+
88
+ ### Fully documented at all times
89
+ Because of the way it works, you cannot implement logging without writing documentation. This means that there will always be documentation.
90
+
91
+ Happy implementers and happy analysts 😊
92
+
93
+ ### Type safety
94
+ #### No unintended values are introduced
95
+
96
+ Because the possible values, such as character limits, patterns, and numeric ranges, are guaranteed at the type level, it prevents unintended values from entering the log database.
97
+
98
+ #### Comfortable to be complemented by IDE
99
+
100
+ Completion during implementation also improves the development experience and reduces mistakes.
101
+
102
+ ![Code Completion](docs/images/code-completion.png)
103
+
104
+ ### Easy to read for both humans and machines
105
+
106
+ Markdown is excellent as a format for log definition documents that anyone could easily read and write.
107
+
108
+ In addition, it was useful to be able to statically analyze both the definition document and the implementation.
109
+
110
+ For example, mistakes such as "I defined it in the document but forgot to implement it" could be detected automatically.
111
+
112
+ This mechanism has actually been useful several times in regular CI runs.
113
+ (It was also useful to be able to notice when a log was accidentally deleted during refactoring, etc.).
114
+
115
+ ## Case Study
116
+
117
+ ### Git version control in the same repository as the app
118
+ In Cookpad (Japan), log definitions and mobile application source code are managed in the same git repository.
119
+
120
+ #### Everything in one place (easy to find)
121
+ It is all in one place, so there is no need to wander around looking for information in logs.
122
+
123
+ #### Background of log definitions can also be checked by following pull requests
124
+
125
+ It is also possible to run `git blame` on log markdown definitions and follow pull requests to find out which version of the log was added, who implemented the log, and with what intention.
126
+
127
+
128
+ ## Articles (Japanese)
129
+ - ドキュメントベースの型安全なモバイルアプリ行動ログ基盤の構築 - クックパッド開発者ブログ https://techlife.cookpad.com/entry/2020/11/05/110000
130
+ - モバイルアプリの行動ログの「仕込み」を快適にする https://speakerdeck.com/yujif/iosdc-japan-2022-mobile-app-logging
131
+
132
+ ## Original Author
133
+ [@giginet](https://github.com/giginet)
134
+
135
+ ## Contributors
136
+ [@hiragram](https://github.com/hiragram)
137
+ [@litmon](https://github.com/litmon)
138
+ [@ksfee684](https://github.com/ksfee684)
139
+ [@aamine](https://github.com/aamine)
140
+ [@vincentisambart](https://github.com/vincentisambart)
141
+ [@kalupas226](https://github.com/kalupas226)
142
+ [@yujif](https://github.com/yujif)
143
+
144
+ ## Contributing
145
+ This repo is basically *read-only*. We publish this code for knowledge sharing purposes.
146
+ Please note that we cannot guarantee continuous maintenance or responses to Issues or Pull Requests.
147
+
148
+ ## License
149
+ MIT License
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/daifuku.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("lib", __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "daifuku/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "daifuku"
9
+ spec.version = Daifuku::VERSION
10
+ spec.authors = ["Kohki Miki"]
11
+ spec.email = ["giginet.net@gmail.com"]
12
+
13
+ spec.summary = "A markdown parser and compiler for log definitions in mobile applications"
14
+ spec.description = <<~EOF
15
+ Daifuku is a markdown parser and compiler for log definitions in mobile applications. It provides an
16
+ automatic generation of document-based type-safe log implementation code from markdown.
17
+ EOF
18
+ spec.homepage = "https://github.com/cookpad/daifuku"
19
+ spec.license = 'MIT'
20
+
21
+ spec.metadata["homepage_uri"] = spec.homepage
22
+ spec.metadata["source_code_uri"] = spec.homepage
23
+
24
+ # Specify which files should be added to the gem when it is released.
25
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
26
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
27
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
+ end
29
+ spec.bindir = "exe"
30
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_dependency "redcarpet", "~> 3.6"
34
+ spec.add_dependency "nokogiri", "~> 1.15"
35
+
36
+ spec.add_development_dependency "bundler", "~> 2.0"
37
+ spec.add_development_dependency "rake", "~> 13.0"
38
+ spec.add_development_dependency "rspec", "~> 3.0"
39
+ end
data/daifuku.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "daifuku/version"
2
+ require "daifuku/compiler"
3
+ require "daifuku/parser"
4
+ require "daifuku/models"
5
+ require "daifuku/renderer"
6
+ require 'daifuku/validator'
7
+
8
+ COMMON_CATEGORY_NAME = 'common'
9
+ COMMON_EVENT_NAME = 'common'
Binary file
Binary file
data/docs/syntax.ja.md ADDED
@@ -0,0 +1,143 @@
1
+ # daifuku ログ定義用 Markdown 形式
2
+
3
+ ## 要素
4
+ ログは**カテゴリ**、**イベント**、**カラム**の3要素からなります。
5
+
6
+ - カテゴリ:イベントの集合。近しい機能を同一のカテゴリとして扱う(`RecipeSearch`など)
7
+ - イベント:ログを送るきっかけとなる動作単位
8
+ - カラム:イベントの持つ値。型と名前を持つ
9
+
10
+ それぞれの名前は `/\w+/` であることが期待されています。
11
+
12
+ # ログ定義の記述
13
+
14
+ ```markdown
15
+ # recipe_search
16
+
17
+ レシピ検索に関するログです。
18
+
19
+ ## search
20
+
21
+ ユーザーがレシピをキーワード検索したときに送信されます。
22
+
23
+ - keyword: !string 256
24
+ - 検索キーワード
25
+ - order: SearchOrder
26
+ - 新着順, 人気順
27
+
28
+ ## show_recipe
29
+
30
+ ユーザーが検索結果画面でレシピを選択したときに送信されます。
31
+
32
+ - recipe_id: !integer
33
+ - 選択されたレシピのID
34
+ - position: !integer
35
+ - タップしたクエリが上から何番目か (0始まり)
36
+ ```
37
+
38
+ ## カテゴリ
39
+
40
+ ドキュメントのトップレベルに記述した文章は全てカテゴリの説明として扱われます。
41
+
42
+ 文中のインライン要素は十分にテストされていません。
43
+ **文章の記述にはボールドやコードブロックなどのインライン要素を使わない方が無難です。**
44
+
45
+ **仕様上はカテゴリ共通カラムの定義が可能ですが、現段階では未対応です。**
46
+
47
+ ファイル名がカテゴリ名として扱われますが、分かりやすさのために、`#` 見出しをファイル名と同じにしましょう。`snake_case`が推奨されます。
48
+
49
+ ## イベント
50
+
51
+ `##` 見出し以下の文章はイベントの説明、カラム定義はイベント内のカラムとして扱われます。
52
+
53
+ 見出しがイベント名として扱われます。`snake_case`が推奨されます。
54
+
55
+ ## カラム
56
+
57
+ イベント以下にあるリスト要素はカラムとして扱われます。カラムは以下のようなシンタックスで定義できます。
58
+
59
+ ```markdown
60
+ - <カラム名>: !<カラム型>(?)
61
+ - カラムの説明1
62
+ - カラムの説明2
63
+ ```
64
+
65
+ ### 型
66
+
67
+ - `!`から始まる型はプリミティブ型です。
68
+ - 末尾に`?`をつけることでオプショナル型として定義することができます。
69
+ - 未知の型を利用したとき、生成に失敗します。
70
+
71
+ クックパッドでは データウェアハウス に Amazon Redshift を利用しており、[Redshift でサポートされているデータ型](https://docs.aws.amazon.com/redshift/latest/dg/c_Supported_data_types.html)を前提として以下の利用可能な型を定義しています。
72
+
73
+ - !smallint : 16ビット符号付き整数
74
+ - !integer : 32ビット符号付き整数
75
+ - !bigint : 64ビット符号付き整数
76
+ - !real : 32ビット浮動小数点数
77
+ - !double : 64ビット浮動小数点数
78
+ - !boolean : 真偽値
79
+ - !string <バイト数> : 文字列型
80
+ - !date : 日付型
81
+ - !timestamp : 日付時刻型(タイムゾーンなし)
82
+ - !timestamptz : 日付時刻型(タイムゾーンあり)
83
+
84
+
85
+ 文字列型は定義時に文字列長の指定が可能です。
86
+
87
+ ```markdown
88
+ - message: !string 256
89
+ ```
90
+
91
+
92
+ ### カスタム型
93
+
94
+ カスタム型を定義、利用することもできます。
95
+ アプリケーション側で enum などを定義して特定の定義域を表現するのに利用できます。
96
+
97
+ ログ定義側でカスタム型を利用するには、`!`から始まらない型名を直接指定してください。
98
+
99
+ ```markdown
100
+ # user
101
+
102
+ ## set_profile
103
+
104
+ - user_status: UserStatus
105
+ - ユーザー状態
106
+ ```
107
+
108
+ ### obsolete指定
109
+
110
+ 利用しなくなったイベントやカラムには `[obsolete]` 指定が使えます。これを含んだイベントやカラムはコード生成から除外されますが、古いログの調査のためにドキュメントには残ります。
111
+
112
+ descriptionに使用できなくなったアプリのバージョン名を明記しておくと便利です。
113
+
114
+ ```markdown
115
+ - [obsolete] user_status: UserStatus
116
+ - ユーザー状態
117
+ - v2020.34.0 から廃止されました
118
+ ```
119
+
120
+ # CommonPayload
121
+
122
+ すべてのイベントが共通して持つカラムを`CommonPayload`と呼んでいます。
123
+ これはUser AgentやユーザーID、OSバージョンなどを想定しています。
124
+
125
+ `common`というカテゴリ名のログ定義は特別に`CommonPayload`として扱われます。
126
+ すなわち、`LogDefinitions/common.md`にカラムを記述することで、`CommonPayload`として処理されます。
127
+
128
+ 記述方法は普通のカテゴリと同様ですが、トップレベルのカラムだけがパースされ、イベント定義は無視されます。
129
+
130
+ ```markdown
131
+ # common
132
+
133
+ - user_id: !bigint?
134
+ - ユーザーID
135
+ - user_agent: !string 1000
136
+ - ユーザーエージェント
137
+ - os_version: !string 32
138
+ - iOSバージョン
139
+ - application_version: !string 32
140
+ - アプリケーションバージョン
141
+ - time_zone: !string 64
142
+ - タイムゾーン(Asia/Tokyoなど)
143
+ ```
@@ -0,0 +1,20 @@
1
+ # common
2
+
3
+ - user_id: !bigint?
4
+ - ユーザーID
5
+ - user_agent: !string 1000
6
+ - ユーザーエージェント
7
+ - os_version: !string 32
8
+ - iOSバージョン
9
+ - application_version: !string 32
10
+ - アプリケーションバージョン
11
+ - time_zone: !string 64
12
+ - タイムゾーン(Asia/Tokyoなど)
13
+ - [obsolete] user_status: !string 32
14
+ - example1, example2, example3
15
+ - device_kind: DeviceKind
16
+ - 端末の種類
17
+ - phone, tablet, unknown
18
+ - device_model: !string 16
19
+ - 端末のモデル
20
+ - 実機だと「iPhoneXX,X」または「iPadXX.X」形式
@@ -0,0 +1,19 @@
1
+ # recipe_search
2
+
3
+ An category of events related to recipe search.
4
+
5
+ ## search
6
+
7
+ An event sent when users perform a recipe search.
8
+
9
+ - keyword: !string 256
10
+ - Search keyword
11
+ - order: SearchOrder
12
+ - latest, popularity
13
+
14
+ ## show_recipe
15
+
16
+ Sent when users move from the search results screen to the recipe details screen.
17
+
18
+ - recipe_id: !integer
19
+ - ID of the selected recipe
@@ -0,0 +1,25 @@
1
+ import Foundation
2
+
3
+ // This file is automatically generated by generate-log-classes.
4
+ public struct CommonPayload {
5
+ <%- columns.each do |column| -%>
6
+ <%- column.descriptions.flat_map(&:lines).each do |description_line| -%>
7
+ /// <%= description_line.strip %>
8
+ <%- end -%>
9
+ var <%= column.variable_name %>: <%= column.swift_type %>
10
+ <%- end -%>
11
+
12
+ public func makePayload() -> [String: JSONCodable] {
13
+ return [
14
+ <%- columns.each do |column| -%>
15
+ "<%= column.original_name %>": <%= column.call_dump %>,
16
+ <%- end -%>
17
+ ].compactMapValues { $0 }
18
+ }
19
+
20
+ public init(<%= columns.map { |column| column.as_argument }.join(', ') -%>) {
21
+ <%- columns.each do |column| -%>
22
+ self.<%= column.variable_name %> = <%= column.variable_name %>
23
+ <%- end -%>
24
+ }
25
+ }
@@ -0,0 +1,33 @@
1
+ // This file is automatically generated by generate-log-classes.
2
+ import Foundation
3
+ <%- categories.each do |category| -%>
4
+ <%- category.descriptions.flat_map(&:lines).each do |description_line| -%>
5
+ /// <%= description_line.strip %>
6
+ <%- end -%>
7
+ public enum <%= category.class_name %> {
8
+ public static var categoryName: String { "<%= category.name %>" }
9
+ public var eventName: String {
10
+ <%- if category.available_events.empty? -%>
11
+ assertionFailure("No events are available in category <%= category.class_name %>")
12
+ return ""
13
+ <%- else -%>
14
+ switch self {
15
+ <%- category.available_events.each do |event| -%>
16
+ case .<%= event.variable_name %>: return "<%= event.name %>"
17
+ <%- end -%>
18
+ }
19
+ <%- end -%>
20
+ }
21
+
22
+ <%- category.events.each do |event| -%>
23
+ <%- event.descriptions.flat_map(&:lines).each do |description_line| -%>
24
+ /// <%= description_line.strip %>
25
+ <%- end -%>
26
+ <%- if event.columns.empty? -%>
27
+ <%= event.availability_annotation %>case <%= event.variable_name %>
28
+ <%- else -%>
29
+ <%= event.availability_annotation %>case <%= event.variable_name %>(<%= event.associated_types %>)
30
+ <%- end -%>
31
+ <%- end -%>
32
+ }
33
+ <%- end -%>