<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Philip Trauner</title><link>https://philip-trauner.me/blog/rss</link><description></description><language>en-US</language><lastBuildDate>Mon, 06 Apr 2026 09:18:05 GMT</lastBuildDate><item><title>Commoditized Social Networking</title><link>https://philip-trauner.me/blog/post/commoditized-social-networking</link><description>Meta's Threads is joining the Fediverse, now what?</description><author>philip.trauner@arztpraxis.io</author><pubDate>Mon, 24 Jul 2023 19:11:48 GMT</pubDate><category>fediverse</category><category>mastodon</category><category>meta</category><category>threads</category><category>commoditization</category><category>social</category><category>graph</category><content:encoded><![CDATA[<p>in a decidedly uncharacteristic move for Meta they've pledged to become part of the Fediverse... and i'm cautiously optimistic.</p>
<p>launching a new social network has always been difficult, because one needs to convince not just interested individuals, but also the communities that they are a part of.</p>
<p>the Fediverse, or more accurately the protocol that powers it, ActivityPub, has the potential to solve this problem by decoupling social <em>network</em> and social <em>graph</em>.<br>
<strong>many networks (or more accurately servers), one shared graph.</strong></p>
<p>there are roughly <a href="https://mastodon.social/@Gargron/110758117937490286">2 million monthly active Mastodon users</a> at the time of writing.<br>
most of them are enthusiasts, who care deeply about the Fediverse' promise.</p>
<p>...but users of centralized social networks generally don't, otherwise they'd have switched already.<br>
converting them by touting the benefits of decentralization, interoperability, or privacy <em>won't work</em>.<br>
this isn't a dig against them, they just chose different battles.</p>
<p>Threads isn't pitching itself as a decentralized social network though. its "an app built by the Instagram team for sharing with text" (<a href="https://about.fb.com/news/2023/07/introducing-threads-new-app-text-sharing/">press statement</a>).<br>
the brand recognition of Instagram, combined with the ongoing instability at Twitter / X, makes it the easy choice for people to flock to.</p>
<p>...and boy are they flocking. Meta was claiming over <a href="https://www.theguardian.com/technology/2023/jul/07/mark-zuckerberg-twitter-killer-threads-hits-sign-ups-two-days">70 million sign-ups two days after launch</a>. even if they are significantly exaggerating, it wouldn't be a surprise if their monthly active user count was to permanently surpass all Mastodon instances.<br>
<strong>only this time, its not a zero-sum game.</strong></p>
<p>once Threads starts federating, those users will become part of the shared social graph, growing it rapidly.</p>
<p>i believe a larger social graph to be desirable.<br>
it will bring perceived legitimacy, that will attract users who would have otherwise ignored the Fediverse.<br>
governments, institutions, public servants, journalists, academics, and others will join, ...but this time they aren't joining a US-owned, venture capital-funded, privacy-hostile, centrally-moderated social network, <strong>they'll be hosting their own servers.</strong></p>
<ul>
<li><a href="https://social.network.europa.eu/about">social.network.europa.eu</a></li>
<li><a href="https://social.bund.de/about">social.bund.de</a></li>
<li><a href="https://social.overheid.nl/about">social.overheid.nl</a></li>
</ul>
<p>with ActivityPub being an open protocol and Mastodon being open source, becoming part of the Fediverse has become downright inexpensive.<br>
and not only that, building a <em>new</em> social network is suddenly not prohibitively expensive anymore.</p>
<p>will this loosen the US' grip on social, and finally enable European companies to enter the market?<br>
<strong>hopefully.</strong></p>
<p>admittedly, there's a risk of this being the first chapter in the "embrace, extend, extinguish" playbook.<br>
Threads will likely be the largest server in the network, a position that they could abuse to push for undesirable ActivityPub changes, while threatening to defederate if things don't go their way.<br>
<strong>the only way to reduce that risk is by making defederation as painful as possible.</strong></p>
<p>thankfully, <a href="https://techcrunch.com/2022/11/21/tumblr-to-add-support-for-activitypub-the-social-protocol-powering-mastodon-and-other-apps/">Tumblr</a> is talking about joining the network, Automattic offers <a href="https://wordpress.org/plugins/activitypub/">an ActivityPub plugin</a> for WordPress, and <a href="https://join-lemmy.org/">Lemmy</a> might become a thing.</p>
<p>lastly, defederation is a two-way street. communities that do not wish to be associated with Meta, can selectively defederate from them, while still being part of the network.</p>
<p><small>i'm excited :)</small></p>]]></content:encoded><guid isPermaLink="true">https://philip-trauner.me/blog/post/commoditized-social-networking</guid></item><item><title>SwiftUI: ScrollView clipping</title><link>https://philip-trauner.me/blog/post/swiftui-scrollview-clips-to-bounds</link><description>ScrollView's clipping behavior can be surprising.</description><pubDate>Sun, 13 Mar 2022 20:48:57 GMT</pubDate><category>swift</category><category>swiftui</category><category>scrollview</category><category>uiscrollview</category><category>shadow</category><content:encoded><![CDATA[<div align="center">
    <img src="/static/blog/post/swiftui-scrollview-clips-to-bounds/content/scrollview-clipping.png" height="200px" />
</div>

<h2>Problem</h2>
<p>Some built-in view modifiers like <a href="https://developer.apple.com/documentation/swiftui/view/shadow(color:radius:x:y:)"><code>.shadow</code></a> <em>do not</em> introduce any additional padding that would increase the intrinsic size of the view.<br>
This can cause problems when views are placed within <code>UIKit</code> hierarchies that enable <a href="https://developer.apple.com/documentation/uikit/uiview/1622415-clipstobounds"><code>.clipsToBounds</code></a>.<br>
This is the case with <code>ScrollView</code>, which internally uses a <a href="https://developer.apple.com/documentation/uikit/uiscrollview"><code>UIScrollView</code></a>.</p>
<h2>Solution</h2>
<h3><code>&gt;=</code> iOS 17</h3>
<p>Use <a href="https://developer.apple.com/documentation/swiftui/view/scrollclipdisabled(_:)"><code>scrollClipDisabled(_:)</code></a></p>
<h3><code>&lt;</code> iOS 17</h3>
<p>Extending <a href="https://developer.apple.com/documentation/uikit/uiscrollview"><code>UIScrollView</code></a> works, because <a href="https://developer.apple.com/documentation/uikit/uiview/1622415-clipstobounds"><code>.clipsToBounds</code></a> is not read-only.</p>
<div class="highlight"><pre><span></span><code><span class="kd">import</span><span class="w"> </span><span class="nc">UIKit</span>

<span class="kd">extension</span><span class="w"> </span><span class="bp">UIScrollView</span><span class="w"> </span><span class="p">{</span>
<span class="w">  </span><span class="n">open</span><span class="w"> </span><span class="kr">override</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">clipsToBounds</span><span class="p">:</span><span class="w"> </span><span class="nb">Bool</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="kr">get</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span><span class="p">}</span>
<span class="w">    </span><span class="kr">set</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">}</span>
<span class="w">  </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>

<p>One should be aware that this is applied globally.</p>]]></content:encoded><guid isPermaLink="true">https://philip-trauner.me/blog/post/swiftui-scrollview-clips-to-bounds</guid></item><item><title>SwiftUI: Dynamic status bar style</title><link>https://philip-trauner.me/blog/post/swift-ui-dynamic-status-bar-style</link><description>A minimally invasive approach to dynamically alter the status bar style in SwiftUI 2 projects.</description><pubDate>Sat, 12 Mar 2022 20:17:07 GMT</pubDate><category>swift</category><category>swiftui</category><category>uistatusbar</category><category>swizzling</category><content:encoded><![CDATA[<p>SwiftUI provides many view modifiers that affect the surrounding view hierarchy, but surprisingly none that alter that status bar style.</p>
<h2>Problem</h2>
<p>In the old days (<em>cough</em> SwiftUI 1) this could be accomplished by subclassing <a href="https://developer.apple.com/documentation/swiftui/uihostingcontroller"><code>UIHostingController</code></a> to override  <a href="https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle"><code>preferredStatusBarStyle</code></a>, which is read-only by default, and using the newly created class as the <a href="https://developer.apple.com/documentation/uikit/uiwindow/1621581-rootviewcontroller"><code>rootViewController</code></a> of the key window. </p>
<div class="highlight"><pre><span></span><code><span class="kd">class</span><span class="w"> </span><span class="nc">HostingController</span><span class="p">&lt;</span><span class="n">Content</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="p">&gt;:</span><span class="w"> </span><span class="n">UIHostingController</span><span class="p">&lt;</span><span class="n">Content</span><span class="p">&gt;</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="kr">override</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">preferredStatusBarStyle</span><span class="p">:</span><span class="w"> </span><span class="n">UIStatusBarStyle</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="p">.</span><span class="n">lightContent</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>

<span class="kd">class</span><span class="w"> </span><span class="nc">SceneDelegate</span><span class="p">:</span><span class="w"> </span><span class="bp">UIResponder</span><span class="p">,</span><span class="w"> </span><span class="bp">UIWindowSceneDelegate</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nv">window</span><span class="p">:</span><span class="w"> </span><span class="bp">UIWindow</span><span class="p">?</span>

<span class="w">    </span><span class="kd">func</span><span class="w"> </span><span class="nf">scene</span><span class="p">(</span>
<span class="w">      </span><span class="kc">_</span><span class="w"> </span><span class="n">scene</span><span class="p">:</span><span class="w"> </span><span class="bp">UIScene</span><span class="p">,</span><span class="w"> </span>
<span class="w">      </span><span class="n">willConnectTo</span><span class="w"> </span><span class="n">session</span><span class="p">:</span><span class="w"> </span><span class="bp">UISceneSession</span><span class="p">,</span><span class="w"> </span>
<span class="w">      </span><span class="n">options</span><span class="w"> </span><span class="n">connectionOptions</span><span class="p">:</span><span class="w"> </span><span class="bp">UIScene</span><span class="p">.</span><span class="n">ConnectionOptions</span>
<span class="w">    </span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="k">guard</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">windowScene</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">scene</span><span class="w"> </span><span class="k">as</span><span class="p">?</span><span class="w"> </span><span class="bp">UIWindowScene</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">}</span>

<span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="nv">rootView</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">RootView</span><span class="p">()</span>

<span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="nv">window</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="bp">UIWindow</span><span class="p">(</span><span class="n">windowScene</span><span class="p">:</span><span class="w"> </span><span class="n">windowScene</span><span class="p">)</span>
<span class="w">        </span><span class="n">window</span><span class="p">.</span><span class="n">rootViewController</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">HostingController</span><span class="p">(</span><span class="n">rootView</span><span class="p">:</span><span class="w"> </span><span class="n">rootView</span><span class="p">)</span>
<span class="w">        </span><span class="kc">self</span><span class="p">.</span><span class="n">window</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">window</span>
<span class="w">        </span><span class="n">window</span><span class="p">.</span><span class="n">makeKeyAndVisible</span><span class="p">()</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>

<figcaption>Old technique</figcaption>

<p>Said solution worked, because the now discouraged  <a href="https://developer.apple.com/documentation/uikit/uiwindowscenedelegate"><code>UIWindowSceneDelegate</code></a> life cycle exposed the app's <code>SceneDelegate</code>. </p>
<p>This is not the case anymore.</p>
<p>There are <a href="https://github.com/xavierdonnellon/swiftui-statusbarstyle/blob/main/StatusBarController.swift">still ways</a> to swap out the <a href="https://developer.apple.com/documentation/swiftui/uihostingcontroller"><code>UIHostingController</code></a> during runtime, but these tend to break features that depend on the new app life cycle being used (e.g.  <a href="https://developer.apple.com/documentation/swiftui/view/onopenurl(perform:)"><code>onOpenURL</code></a>).</p>
<h2>Solution</h2>
<h3><code>&gt;=</code> iOS 16</h3>
<p>Use <a href="https://developer.apple.com/documentation/swiftui/view/toolbarcolorscheme(_:for:)"><code>toolbarColorScheme(_:for:)</code></a></p>
<h3><code>&lt;</code> iOS 16</h3>
<p>Since swapping in a custom <a href="https://developer.apple.com/documentation/swiftui/uihostingcontroller"><code>UIHostingController</code></a> isn't an option, a minimally invasive approach involves swizzling <code>preferredStatusBarStyle</code> to point to a computed variable which itself points to a writeable variable.</p>
<blockquote>
<p>Method swizzling is a technique of last resort; you should only use it if you have no other options.</p>
<p>—  <a href="https://developer.apple.com/forums/thread/48047?answerId=141282022#141282022">eskimo</a> </p>
</blockquote>
<div class="highlight"><pre><span></span><code><span class="kd">extension</span><span class="w"> </span><span class="bp">UIViewController</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="n">fileprivate</span><span class="w"> </span><span class="kd">enum</span><span class="w"> </span><span class="nc">Holder</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="kd">static</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">statusBarStyleStack</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="n">UIStatusBarStyle</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="kd">init</span><span class="p">()</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="n">fileprivate</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">interpose</span><span class="p">()</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="nb">Bool</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="nv">sel1</span><span class="p">:</span><span class="w"> </span><span class="nb">Selector</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">#selector</span><span class="p">(</span>
<span class="w">            </span><span class="n">getter</span><span class="p">:</span><span class="w"> </span><span class="n">preferredStatusBarStyle</span>
<span class="w">        </span><span class="p">)</span>
<span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="nv">sel2</span><span class="p">:</span><span class="w"> </span><span class="nb">Selector</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">#selector</span><span class="p">(</span>
<span class="w">            </span><span class="n">getter</span><span class="p">:</span><span class="w"> </span><span class="n">preferredStatusBarStyleModified</span>
<span class="w">        </span><span class="p">)</span>

<span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="nv">original</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">class_getInstanceMethod</span><span class="p">(</span><span class="kc">Self</span><span class="p">.</span><span class="kc">self</span><span class="p">,</span><span class="w"> </span><span class="n">sel1</span><span class="p">)</span>
<span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="nv">new</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">class_getInstanceMethod</span><span class="p">(</span><span class="kc">Self</span><span class="p">.</span><span class="kc">self</span><span class="p">,</span><span class="w"> </span><span class="n">sel2</span><span class="p">)</span>

<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">original</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">original</span><span class="p">,</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">new</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">new</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="n">method_exchangeImplementations</span><span class="p">(</span><span class="n">original</span><span class="p">,</span><span class="w"> </span><span class="n">new</span><span class="p">)</span>

<span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span>
<span class="w">        </span><span class="p">}</span>

<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="kr">@objc</span><span class="w"> </span><span class="kr">dynamic</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">preferredStatusBarStyleModified</span><span class="p">:</span><span class="w"> </span><span class="n">UIStatusBarStyle</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="n">Holder</span><span class="p">.</span><span class="n">statusBarStyleStack</span><span class="p">.</span><span class="bp">last</span><span class="w"> </span><span class="p">??</span><span class="w"> </span><span class="p">.</span><span class="k">default</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>

<p>Some additional scaffolding is required to implement a <code>.statusBarStyle</code> view modifier.</p>
<div class="highlight"><pre><span></span><code><span class="kd">import</span><span class="w"> </span><span class="nc">SwiftUI</span>

<span class="kd">enum</span><span class="w"> </span><span class="nc">Interposed</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="n">pending</span>
<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="n">successful</span>
<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="n">failed</span>
<span class="p">}</span>

<span class="kd">struct</span><span class="w"> </span><span class="nc">InterposedKey</span><span class="p">:</span><span class="w"> </span><span class="n">EnvironmentKey</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="kd">static</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">defaultValue</span><span class="p">:</span><span class="w"> </span><span class="n">Interposed</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">pending</span>
<span class="p">}</span>

<span class="kd">extension</span><span class="w"> </span><span class="nc">EnvironmentValues</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="n">fileprivate</span><span class="p">(</span><span class="kr">set</span><span class="p">)</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">interposed</span><span class="p">:</span><span class="w"> </span><span class="n">Interposed</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="kr">get</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kc">self</span><span class="p">[</span><span class="n">InterposedKey</span><span class="p">.</span><span class="kc">self</span><span class="p">]</span><span class="w"> </span><span class="p">}</span>
<span class="w">        </span><span class="kr">set</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kc">self</span><span class="p">[</span><span class="n">InterposedKey</span><span class="p">.</span><span class="kc">self</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">newValue</span><span class="w"> </span><span class="p">}</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>

<span class="c1">/// `UIApplication.keyWindow` is deprecated</span>
<span class="kd">extension</span><span class="w"> </span><span class="bp">UIApplication</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nv">keyWindow</span><span class="p">:</span><span class="w"> </span><span class="bp">UIWindow</span><span class="p">?</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="n">connectedScenes</span>
<span class="w">            </span><span class="p">.</span><span class="n">compactMap</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$0</span><span class="w"> </span><span class="k">as</span><span class="p">?</span><span class="w"> </span><span class="bp">UIWindowScene</span><span class="w"> </span><span class="p">}</span>
<span class="w">            </span><span class="p">.</span><span class="n">flatMap</span><span class="p">(</span><span class="err">\</span><span class="p">.</span><span class="n">windows</span><span class="p">)</span>
<span class="w">            </span><span class="p">.</span><span class="bp">first</span><span class="w"> </span><span class="p">{</span>
<span class="w">                </span><span class="nv">$0</span><span class="p">.</span><span class="n">isKeyWindow</span>
<span class="w">            </span><span class="p">}</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>

<span class="kd">extension</span><span class="w"> </span><span class="bp">UIViewController</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="n">fileprivate</span><span class="w"> </span><span class="kd">enum</span><span class="w"> </span><span class="nc">Holder</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="kd">static</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">statusBarStyleStack</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="n">UIStatusBarStyle</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="kd">init</span><span class="p">()</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="n">fileprivate</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">interpose</span><span class="p">()</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="nb">Bool</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="nv">sel1</span><span class="p">:</span><span class="w"> </span><span class="nb">Selector</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">#selector</span><span class="p">(</span>
<span class="w">            </span><span class="n">getter</span><span class="p">:</span><span class="w"> </span><span class="n">preferredStatusBarStyle</span>
<span class="w">        </span><span class="p">)</span>
<span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="nv">sel2</span><span class="p">:</span><span class="w"> </span><span class="nb">Selector</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">#selector</span><span class="p">(</span>
<span class="w">            </span><span class="n">getter</span><span class="p">:</span><span class="w"> </span><span class="n">preferredStatusBarStyleModified</span>
<span class="w">        </span><span class="p">)</span>

<span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="nv">original</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">class_getInstanceMethod</span><span class="p">(</span><span class="kc">Self</span><span class="p">.</span><span class="kc">self</span><span class="p">,</span><span class="w"> </span><span class="n">sel1</span><span class="p">)</span>
<span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="nv">new</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">class_getInstanceMethod</span><span class="p">(</span><span class="kc">Self</span><span class="p">.</span><span class="kc">self</span><span class="p">,</span><span class="w"> </span><span class="n">sel2</span><span class="p">)</span>

<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">original</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">original</span><span class="p">,</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">new</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">new</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="n">method_exchangeImplementations</span><span class="p">(</span><span class="n">original</span><span class="p">,</span><span class="w"> </span><span class="n">new</span><span class="p">)</span>

<span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span>
<span class="w">        </span><span class="p">}</span>

<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="kr">@objc</span><span class="w"> </span><span class="kr">dynamic</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">preferredStatusBarStyleModified</span><span class="p">:</span><span class="w"> </span><span class="n">UIStatusBarStyle</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="n">Holder</span><span class="p">.</span><span class="n">statusBarStyleStack</span><span class="p">.</span><span class="bp">last</span><span class="w"> </span><span class="p">??</span><span class="w"> </span><span class="p">.</span><span class="k">default</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>

<span class="kd">struct</span><span class="w"> </span><span class="nc">StatusBarStyle</span><span class="p">:</span><span class="w"> </span><span class="n">ViewModifier</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="p">@</span><span class="n">Environment</span><span class="p">(</span><span class="err">\</span><span class="p">.</span><span class="n">interposed</span><span class="p">)</span><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">interposed</span>

<span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="nv">statusBarStyle</span><span class="p">:</span><span class="w"> </span><span class="n">UIStatusBarStyle</span>
<span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="nv">animationDuration</span><span class="p">:</span><span class="w"> </span><span class="n">TimeInterval</span>

<span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">setStatusBarStyle</span><span class="p">(</span><span class="kc">_</span><span class="w"> </span><span class="n">statusBarStyle</span><span class="p">:</span><span class="w"> </span><span class="n">UIStatusBarStyle</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="bp">UIViewController</span><span class="p">.</span><span class="n">Holder</span><span class="p">.</span><span class="n">statusBarStyleStack</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">statusBarStyle</span><span class="p">)</span>

<span class="w">        </span><span class="bp">UIView</span><span class="p">.</span><span class="n">animate</span><span class="p">(</span><span class="n">withDuration</span><span class="p">:</span><span class="w"> </span><span class="n">animationDuration</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="bp">UIApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">keyWindow</span><span class="p">?.</span><span class="n">rootViewController</span><span class="p">?.</span><span class="n">setNeedsStatusBarAppearanceUpdate</span><span class="p">()</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="kd">func</span><span class="w"> </span><span class="nf">body</span><span class="p">(</span><span class="n">content</span><span class="p">:</span><span class="w"> </span><span class="n">Content</span><span class="p">)</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="n">content</span>
<span class="w">            </span><span class="p">.</span><span class="n">onAppear</span><span class="w"> </span><span class="p">{</span>
<span class="w">                </span><span class="n">setStatusBarStyle</span><span class="p">(</span><span class="n">statusBarStyle</span><span class="p">)</span>
<span class="w">            </span><span class="p">}</span>
<span class="w">            </span><span class="p">.</span><span class="n">onChange</span><span class="p">(</span><span class="n">of</span><span class="p">:</span><span class="w"> </span><span class="n">statusBarStyle</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">                </span><span class="n">setStatusBarStyle</span><span class="p">(</span><span class="nv">$0</span><span class="p">)</span>
<span class="w">                </span><span class="bp">UIViewController</span><span class="p">.</span><span class="n">Holder</span><span class="p">.</span><span class="n">statusBarStyleStack</span><span class="p">.</span><span class="n">removeFirst</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="w">            </span><span class="p">}</span>
<span class="w">            </span><span class="p">.</span><span class="n">onDisappear</span><span class="w"> </span><span class="p">{</span>
<span class="w">                </span><span class="bp">UIViewController</span><span class="p">.</span><span class="n">Holder</span><span class="p">.</span><span class="n">statusBarStyleStack</span><span class="p">.</span><span class="n">removeFirst</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>

<span class="w">                </span><span class="bp">UIView</span><span class="p">.</span><span class="n">animate</span><span class="p">(</span><span class="n">withDuration</span><span class="p">:</span><span class="w"> </span><span class="n">animationDuration</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">                    </span><span class="bp">UIApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">keyWindow</span><span class="p">?.</span><span class="n">rootViewController</span><span class="p">?.</span><span class="n">setNeedsStatusBarAppearanceUpdate</span><span class="p">()</span>
<span class="w">                </span><span class="p">}</span>
<span class="w">            </span><span class="p">}</span>
<span class="w">            </span><span class="c1">// Interposing might still be pending on initial render</span>
<span class="w">            </span><span class="p">.</span><span class="n">onChange</span><span class="p">(</span><span class="n">of</span><span class="p">:</span><span class="w"> </span><span class="n">interposed</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kc">_</span><span class="w"> </span><span class="k">in</span>
<span class="w">                </span><span class="bp">UIView</span><span class="p">.</span><span class="n">animate</span><span class="p">(</span><span class="n">withDuration</span><span class="p">:</span><span class="w"> </span><span class="n">animationDuration</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">                    </span><span class="bp">UIApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">keyWindow</span><span class="p">?.</span><span class="n">rootViewController</span><span class="p">?.</span><span class="n">setNeedsStatusBarAppearanceUpdate</span><span class="p">()</span>
<span class="w">                </span><span class="p">}</span>
<span class="w">            </span><span class="p">}</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>

<span class="kd">extension</span><span class="w"> </span><span class="nc">View</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="kd">func</span><span class="w"> </span><span class="nf">statusBarStyle</span><span class="p">(</span>
<span class="w">        </span><span class="kc">_</span><span class="w"> </span><span class="n">statusBarStyle</span><span class="p">:</span><span class="w"> </span><span class="n">UIStatusBarStyle</span><span class="p">,</span>
<span class="w">        </span><span class="n">animationDuration</span><span class="p">:</span><span class="w"> </span><span class="n">TimeInterval</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mf">0.3</span>
<span class="w">    </span><span class="p">)</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="n">modifier</span><span class="p">(</span><span class="n">StatusBarStyle</span><span class="p">(</span><span class="n">statusBarStyle</span><span class="p">:</span><span class="w"> </span><span class="n">statusBarStyle</span><span class="p">,</span><span class="w"> </span><span class="n">animationDuration</span><span class="p">:</span><span class="w"> </span><span class="n">animationDuration</span><span class="p">))</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>


<span class="p">@</span><span class="n">main</span>
<span class="kd">struct</span><span class="w"> </span><span class="nc">YourApp</span><span class="p">:</span><span class="w"> </span><span class="n">App</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="p">@</span><span class="n">Environment</span><span class="p">(</span><span class="err">\</span><span class="p">.</span><span class="n">scenePhase</span><span class="p">)</span><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">scenePhase</span>

<span class="w">    </span><span class="c1">/// Ensures that interposing only occurs once</span>
<span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">interposeLock</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="bp">NSLock</span><span class="p">()</span>

<span class="w">    </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">interposed</span><span class="p">:</span><span class="w"> </span><span class="n">Interposed</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">pending</span>

<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">Scene</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="n">WindowGroup</span><span class="w"> </span><span class="p">{</span>
<span class="w">            </span><span class="n">VStack</span><span class="w"> </span><span class="p">{</span>
<span class="w">                </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Hello, world!&quot;</span><span class="p">)</span>
<span class="w">                    </span><span class="p">.</span><span class="n">padding</span><span class="p">()</span>
<span class="w">            </span><span class="p">}</span>
<span class="w">            </span><span class="p">.</span><span class="n">statusBarStyle</span><span class="p">(.</span><span class="n">lightContent</span><span class="p">)</span>
<span class="w">            </span><span class="p">.</span><span class="n">environment</span><span class="p">(</span><span class="err">\</span><span class="p">.</span><span class="n">interposed</span><span class="p">,</span><span class="w"> </span><span class="n">interposed</span><span class="p">)</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">        </span><span class="p">.</span><span class="n">onChange</span><span class="p">(</span><span class="n">of</span><span class="p">:</span><span class="w"> </span><span class="n">scenePhase</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">phase</span><span class="w"> </span><span class="k">in</span>
<span class="w">            </span><span class="c1">/// `keyWindow` isn&#39;t set before first `scenePhase` transition</span>
<span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">active</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">phase</span><span class="w"> </span><span class="p">{</span>
<span class="w">                </span><span class="n">interposeLock</span><span class="p">.</span><span class="n">lock</span><span class="p">()</span>
<span class="w">                </span><span class="k">if</span><span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">pending</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">interposed</span><span class="p">,</span>
<span class="w">                   </span><span class="k">case</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="bp">UIApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">keyWindow</span><span class="p">?.</span><span class="n">rootViewController</span><span class="p">?.</span><span class="n">interpose</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w">                    </span><span class="n">interposed</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">successful</span>
<span class="w">                </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span>
<span class="w">                    </span><span class="n">interposed</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">failed</span>
<span class="w">                </span><span class="p">}</span>
<span class="w">                </span><span class="n">interposeLock</span><span class="p">.</span><span class="n">unlock</span><span class="p">()</span>
<span class="w">            </span><span class="p">}</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>

<p>The <code>.\interposed</code> environment value can used to detect swizzling failure.  A potential fallback could involve filling the safe area beneath the status bar with an adaptive color.</p>
<div class="highlight"><pre><span></span><code><span class="kd">struct</span><span class="w"> </span><span class="nc">Fallback</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span>
<span class="w">  </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="n">GeometryReader</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">reader</span><span class="w"> </span><span class="k">in</span>
<span class="w">      </span><span class="n">Color</span><span class="p">(</span><span class="n">uiColor</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">systemBackground</span><span class="p">)</span>
<span class="w">        </span><span class="p">.</span><span class="n">opacity</span><span class="p">(</span><span class="mf">0.4</span><span class="p">)</span>
<span class="w">        </span><span class="p">.</span><span class="n">frame</span><span class="p">(</span><span class="n">height</span><span class="p">:</span><span class="w"> </span><span class="n">reader</span><span class="p">.</span><span class="n">safeAreaInsets</span><span class="p">.</span><span class="n">top</span><span class="p">)</span>
<span class="w">        </span><span class="p">.</span><span class="n">edgesIgnoringSafeArea</span><span class="p">(.</span><span class="n">top</span><span class="p">)</span>
<span class="w">    </span><span class="p">}</span>
<span class="w">  </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>]]></content:encoded><guid isPermaLink="true">https://philip-trauner.me/blog/post/swift-ui-dynamic-status-bar-style</guid></item><item><title>Obtaining Apple Health Workout Icons</title><link>https://philip-trauner.me/blog/post/fitness-ui-icons</link><description>Acquiring the official workout icons used across the Apple Health ecosystem is surprisingly difficult.</description><pubDate>Sat, 26 Dec 2020 16:40:53 GMT</pubDate><category>swift</category><category>ios</category><category>healthkit</category><category>fitnessui</category><category>workout</category><category>icon</category><content:encoded><![CDATA[<style>
/* https://github.com/Python-Markdown/markdown/issues/845 */
.markdown-body li > p {
    margin-top: 0px;
}
</style>

<p>I've recently been playing around with <code>SwiftUI</code> and <code>HealthKit</code>.<br>
Naturally, the first thing I wanted to try out was to display a list of all workouts and their respective types.</p>
<p>Can't be that difficult, right?</p>
<h2>🧪 Discovery</h2>
<p>SF Symbols contains <em>some</em> health-related icons, but none that depict workout types (except walking).<br>
The <code>HealthKit</code> documentation also doesn't mention anything in regards to workout icons or even localized names.</p>
<p>After a bit of searching I stumbled across references to <code>FitnessUI.framework</code>, which I assumed to be responsible for presentational aspects of Apple's own <code>HealthKit</code> apps.</p>
<p>This was coincidentally also the first time I found out about private frameworks, which one can theoretically use, but only if violating App Store guidelines isn't a concern.<br>
<code>FitnessUI</code> is one such framework, but because there's at least <a href="https://apps.apple.com/us/app/healthfit/id1202650514">one</a> app that not only uses the official icons, but also displays them on its store page, I figured that icons might be an accepted gray-area.</p>
<p><code>FitnessUI.framework</code> is not included in macOS builds, therefor I took apart an iOS <code>.ipsw</code> to extract it from the contained root filesystem.<br>
I discovered an <code>Assets.car</code> file contained within the framework, which I managed to extract with <a href="https://twitter.com/_inside">@_inside</a>'s <a href="https://github.com/insidegui/AssetCatalogTinkerer">Asset Catalog Tinkerer</a>.</p>
<p align="center"><img src="/static/blog/post/fitness-ui-icons/content/asset-catalog.png" width="500px"/></p>
<figcaption>Asset Catalog Tinkerer</figcaption>

<p>After extracting all icons it became apparent that there were a few outliers in Apple's naming scheme.</p>
<div class="highlight"><pre><span></span><code><span class="p">[</span>
<span class="w">    </span><span class="s2">&quot;_112_Normal@3x.png&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="s2">&quot;_112px-1_Normal@3x.png&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="s2">&quot;_112px_Normal@3x.png&quot;</span><span class="p">,</span>
<span class="p">]</span>
</code></pre></div>

<figcaption>Suffixes of icons</figcaption>

<p>Some workout types also had different names than their <a href="https://developer.apple.com/documentation/healthkit/hkworkoutactivitytype"><code>HKWorkoutActivityType</code></a> counterparts.</p>
<div class="highlight"><pre><span></span><code><span class="p">{</span>
<span class="w">    </span><span class="nt">&quot;australianFootball&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;australian_rules_football&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;cardioDance&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;dance&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;cycling&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;outdoorcycle&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;danceInspiredTraining&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;dance_insp_training&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;discSports&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;disk_sports&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;fitnessGaming&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;gaming_sports&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;functionalStrengthTraining&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;func_strength_training&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;highIntensityIntervalTraining&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;hiit&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;mixedCardio&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;mixed_meta_cardio_training&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;mixedMetabolicCardioTraining&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;mixed_meta_cardio_training&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;preparationAndRecovery&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;prep_and_recovery&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;running&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;outdoorrun&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;socialDance&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;social-dance&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;stairClimbing&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;stairs&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;surfingSports&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;surfing&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;swimming&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;swimopen&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;traditionalStrengthTraining&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;trad_weight_training&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;walking&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;outdoorwalk&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;wheelchairRunPace&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;wheelchairrun&quot;</span><span class="p">,</span>
<span class="w">    </span><span class="nt">&quot;wheelchairWalkPace&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;wheelchairwalk&quot;</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div>

<figcaption>Rewrite rules for icon names</figcaption>

<p>There are quite a few workout types, and new once are introduced frequently, which is why I ended up automating the process of generating an <a href="https://developer.apple.com/documentation/healthkit/hkworkoutactivitytype"><code>HKWorkoutActivityType</code></a> extension that correlates workout types and their respective icons.</p>
<h2>⚙️ Process</h2>
<ol>
<li><a href="https://ipsw.me/">Download</a> a recent iOS update <code>.ipsw</code></li>
<li>Change its file extension to <code>.zip</code> and consequently extract it<br>
    <p align="center"><img src="/static/blog/post/fitness-ui-icons/content/rename-to-zip.png" width="500px"/></p></li>
<li>Open the largest contained <code>.dmg</code><br>
    <p align="center"><img src="/static/blog/post/fitness-ui-icons/content/largest-dmg.png" width="500px"/></p></li>
<li>Navigate to <code>System/Library/PrivateFrameworks/FitnessUI.framework</code><br>
    <p align="center"><img src="/static/blog/post/fitness-ui-icons/content/selected-asset-catalog.png" width="500px"/></p></li>
<li>Extract <code>Assets.car</code></li>
<li>Open extracted asset catalog with <a href="https://github.com/insidegui/AssetCatalogTinkerer">Asset Catalog Tinkerer</a></li>
<li>Profit!</li>
</ol>
<h2>🪜 Usage</h2>
<p>That covers the extraction aspect, but one should ideally be able to correlate the icons with their respective <a href="https://developer.apple.com/documentation/healthkit/hkworkoutactivitytype"><code>HKWorkoutActivityType</code></a> variants.</p>
<div class="highlight"><pre><span></span><code><span class="kd">import</span><span class="w"> </span><span class="nc">Foundation</span>
<span class="kd">import</span><span class="w"> </span><span class="nc">HealthKit</span>

<span class="kd">extension</span><span class="w"> </span><span class="nc">HKWorkoutActivityType</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="k">switch</span><span class="w"> </span><span class="kc">self</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">americanFootball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;American Football&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">archery</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Archery&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">australianFootball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Australian Football&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">badminton</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Badminton&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">barre</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Barre&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">baseball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Baseball&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">basketball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Basketball&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">bowling</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Bowling&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">boxing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Boxing&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">cardioDance</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Cardio Dance&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">climbing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Climbing&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">cooldown</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Cooldown&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">coreTraining</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Core Training&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">cricket</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Cricket&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">crossCountrySkiing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Cross Country Skiing&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">crossTraining</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Cross Training&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">curling</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Curling&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">cycling</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Cycling&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">dance</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Dance&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">danceInspiredTraining</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Dance Inspired Training&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">discSports</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Disc Sports&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">downhillSkiing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Downhill Skiing&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">elliptical</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Elliptical&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">equestrianSports</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Equestrian Sports&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">fencing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Fencing&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">fishing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Fishing&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">fitnessGaming</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Fitness Gaming&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">flexibility</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Flexibility&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">functionalStrengthTraining</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Functional Strength Training&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">golf</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Golf&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">gymnastics</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Gymnastics&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">handCycling</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Hand Cycling&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">handball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Handball&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">highIntensityIntervalTraining</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;High Intensity Interval Training&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">hiking</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Hiking&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">hockey</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Hockey&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">hunting</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Hunting&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">jumpRope</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Jump Rope&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">kickboxing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Kickboxing&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">lacrosse</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Lacrosse&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">martialArts</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Martial Arts&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">mindAndBody</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Mind and Body&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">mixedCardio</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Mixed Cardio&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">mixedMetabolicCardioTraining</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Mixed Metabolic Cardio Training&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">other</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Other&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">paddleSports</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Paddle Sports&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">pickleball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Pickleball&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">pilates</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Pilates&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">play</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Play&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">preparationAndRecovery</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Preparation and Recovery&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">racquetball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Racquetball&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">rowing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Rowing&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">rugby</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Rugby&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">running</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Running&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">sailing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Sailing&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">skatingSports</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Skating Sports&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">snowSports</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Snow Sports&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">snowboarding</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Snowboarding&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">soccer</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Soccer&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">socialDance</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Social Dance&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">softball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Softball&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">squash</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Squash&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">stairClimbing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Stair Climbing&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">stairs</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Stairs&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">stepTraining</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Step Training&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">surfingSports</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Surfing Sports&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">swimming</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Swimming&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">tableTennis</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Table Tennis&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">taiChi</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Tai Chi&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">tennis</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Tennis&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">trackAndField</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Track and Field&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">traditionalStrengthTraining</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Traditional Strength Training&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">volleyball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Volleyball&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">walking</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Walking&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">waterFitness</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Water Fitness&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">waterPolo</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Water Polo&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">waterSports</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Water Sports&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">wheelchairRunPace</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Wheelchair Run Pace&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">wheelchairWalkPace</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Wheelchair Walk Pace&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">wrestling</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Wrestling&quot;</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">yoga</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Yoga&quot;</span>
<span class="w">        </span><span class="k">default</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Unknown&quot;</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">    </span><span class="p">}</span>

<span class="w">    </span><span class="kd">static</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">allCases</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="n">HKWorkoutActivityType</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span>
<span class="w">        </span><span class="p">.</span><span class="n">americanFootball</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">archery</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">australianFootball</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">badminton</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">barre</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">baseball</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">basketball</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">bowling</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">boxing</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">cardioDance</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">climbing</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">cooldown</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">coreTraining</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">cricket</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">crossCountrySkiing</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">crossTraining</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">curling</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">cycling</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">dance</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">danceInspiredTraining</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">discSports</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">downhillSkiing</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">elliptical</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">equestrianSports</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">fencing</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">fishing</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">fitnessGaming</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">flexibility</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">functionalStrengthTraining</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">golf</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">gymnastics</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">handCycling</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">handball</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">highIntensityIntervalTraining</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">hiking</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">hockey</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">hunting</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">jumpRope</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">kickboxing</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">lacrosse</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">martialArts</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">mindAndBody</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">mixedCardio</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">mixedMetabolicCardioTraining</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">other</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">paddleSports</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">pickleball</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">pilates</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">play</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">preparationAndRecovery</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">racquetball</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">rowing</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">rugby</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">running</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">sailing</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">skatingSports</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">snowSports</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">snowboarding</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">soccer</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">socialDance</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">softball</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">squash</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">stairClimbing</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">stairs</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">stepTraining</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">surfingSports</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">swimming</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">tableTennis</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">taiChi</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">tennis</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">trackAndField</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">traditionalStrengthTraining</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">volleyball</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">walking</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">waterFitness</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">waterPolo</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">waterSports</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">wheelchairRunPace</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">wheelchairWalkPace</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">wrestling</span><span class="p">,</span>
<span class="w">        </span><span class="p">.</span><span class="n">yoga</span>
<span class="w">    </span><span class="p">]</span>

<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nv">url</span><span class="p">:</span><span class="w"> </span><span class="n">URL</span><span class="p">?</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="k">switch</span><span class="w"> </span><span class="kc">self</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">americanFootball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;american_football_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">archery</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;archery_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">australianFootball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;australian_rules_football_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">badminton</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;badminton_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">barre</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;barre_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">baseball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;baseball_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">basketball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;basketball_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">bowling</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;bowling_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">boxing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;boxing_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">cardioDance</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;dance_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">climbing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;climbing_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">cooldown</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;cooldown_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">coreTraining</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;core_training_112px-1_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">cricket</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;cricket_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">crossCountrySkiing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;cross_country_skiing_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">crossTraining</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;cross_training_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">curling</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;curling_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">cycling</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;outdoorcycle_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">dance</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;dance_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">danceInspiredTraining</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;dance_insp_training_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">discSports</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;disk_sports_112px-1_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">downhillSkiing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;downhill_skiing_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">elliptical</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;elliptical_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">equestrianSports</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;equestrian_sports_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">fencing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;fencing_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">fishing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;fishing_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">fitnessGaming</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;gaming_sports_112px-1_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">flexibility</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;flexibility_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">functionalStrengthTraining</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;func_strength_training_112_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">golf</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;golf_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">gymnastics</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;gymnastics_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">handCycling</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;hand_cycling_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">handball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;handball_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">highIntensityIntervalTraining</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;hiit_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">hiking</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;hiking_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">hockey</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;hockey_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">hunting</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;hunting_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">jumpRope</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;jump_rope_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">kickboxing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;kickboxing_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">lacrosse</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;lacrosse_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">martialArts</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;martial_arts_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">mindAndBody</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;mind_and_body_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">mixedCardio</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;mixed_meta_cardio_training_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">mixedMetabolicCardioTraining</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;mixed_meta_cardio_training_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">other</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;other_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">paddleSports</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;paddle_sports_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">pickleball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;pickleball_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">pilates</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;pilates_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">play</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;play_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">preparationAndRecovery</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;prep_and_recovery_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">racquetball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;racquetball_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">rowing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;rowing_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">rugby</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;rugby_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">running</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;outdoorrun_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">sailing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;sailing_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">skatingSports</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;skating_sports_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">snowSports</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;snow_sports_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">snowboarding</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;snowboarding_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">soccer</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;soccer_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">socialDance</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;social-dance_112px-1_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">softball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;softball_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">squash</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;squash_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">stairClimbing</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;stairs_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">stairs</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;stairs_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">stepTraining</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;step_training_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">surfingSports</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;surfing_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">swimming</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;swimopen_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">tableTennis</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;table_tennis_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">taiChi</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;tai_chi_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">tennis</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;tennis_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">trackAndField</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;track_and_field_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">traditionalStrengthTraining</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;trad_weight_training_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">volleyball</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;volleyball_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">walking</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;outdoorwalk_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">waterFitness</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;water_fitness_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">waterPolo</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;water_polo_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">waterSports</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;water_sports_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">wheelchairRunPace</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;wheelchairrun_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">wheelchairWalkPace</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;wheelchairwalk_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">wrestling</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;wrestling_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="p">.</span><span class="n">yoga</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">Bundle</span><span class="p">.</span><span class="n">main</span><span class="p">.</span><span class="n">url</span><span class="p">(</span><span class="n">forResource</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;yoga_112px_Normal@3x&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">withExtension</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;png&quot;</span><span class="p">)</span>
<span class="w">        </span><span class="k">default</span><span class="p">:</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span>
<span class="w">        </span><span class="p">}</span>
<span class="w">    </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>

<figcaption><code>HKWorkoutActivityType.swift</code></figcaption>

<details><summary>Script used to generate the extension</summary>

<ol>
    <li>
    <p>Create following folder structure</p>
    <pre>
├── script.py
├── assets
│   ├── all
│   │   └── FitnessUI
└── pyproject.toml</pre>
    </li>
    <li>Copy extracted assets into <code>assets/all/FitnessUI</code></li>
    <li>Run <code>script.py</code></li>
</ol>

<p>All icons that have an associated <a href="https://developer.apple.com/documentation/healthkit/hkworkoutactivitytype"><code>HKWorkoutActivityType</code></a> variant are copied over to <code>assets/reduced/FitnessUI</code>, and the generated code is printed to <code>stdout</code>.</p>


<div class="highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">dataclasses</span><span class="w"> </span><span class="kn">import</span> <span class="n">dataclass</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">itertools</span><span class="w"> </span><span class="kn">import</span> <span class="n">chain</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">pathlib</span><span class="w"> </span><span class="kn">import</span> <span class="n">Path</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">string</span><span class="w"> </span><span class="kn">import</span> <span class="n">Template</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">List</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">Optional</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">shutil</span><span class="w"> </span><span class="kn">import</span> <span class="n">copy</span>

<span class="kn">from</span><span class="w"> </span><span class="nn">httpx</span><span class="w"> </span><span class="kn">import</span> <span class="n">get</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">inflection</span><span class="w"> </span><span class="kn">import</span> <span class="n">titleize</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">inflection</span><span class="w"> </span><span class="kn">import</span> <span class="n">underscore</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">jmespath</span><span class="w"> </span><span class="kn">import</span> <span class="n">search</span>


<span class="k">def</span><span class="w"> </span><span class="nf">removesuffixes</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">suffixes</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="k">for</span> <span class="n">suffix</span> <span class="ow">in</span> <span class="n">suffixes</span><span class="p">:</span>
        <span class="n">applied</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">removesuffix</span><span class="p">(</span><span class="n">suffix</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">applied</span> <span class="o">!=</span> <span class="n">text</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">applied</span>
    <span class="k">return</span> <span class="n">text</span>


<span class="n">HK_WORKOUT_ACTIVITY_TYPE_EXTENSION_TEMPLATE</span> <span class="o">=</span> <span class="n">Template</span><span class="p">(</span>
<span class="w">    </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd">import Foundation</span>
<span class="sd">import HealthKit</span>

<span class="sd">extension HKWorkoutActivityType {</span>
<span class="sd">    var name: String {</span>
<span class="sd">        switch self {</span>
<span class="sd">$name</span>
<span class="sd">        default: return &quot;Unknown&quot;</span>
<span class="sd">        }</span>
<span class="sd">    }</span>

<span class="sd">    static let allCases: [HKWorkoutActivityType] = [</span>
<span class="sd">$all_cases</span>
<span class="sd">    ]</span>

<span class="sd">    var url: URL? {</span>
<span class="sd">        switch self {</span>
<span class="sd">$url</span>
<span class="sd">        default: return nil</span>
<span class="sd">        }</span>
<span class="sd">    }</span>
<span class="sd">}</span>
<span class="sd">&quot;&quot;&quot;</span><span class="o">.</span><span class="n">lstrip</span><span class="p">()</span>
<span class="p">)</span>

<span class="n">HK_WORKOUT_ACTIVITY_TYPE_EXTENSION_NAME_TEMPLATE</span> <span class="o">=</span> <span class="n">Template</span><span class="p">(</span>
    <span class="s1">&#39;        case .$case: return &quot;$humanized&quot;&#39;</span>
<span class="p">)</span>
<span class="n">HK_WORKOUT_ACTIVITY_TYPE_EXTENSION_ALL_CASES_TEMPLATE</span> <span class="o">=</span> <span class="n">Template</span><span class="p">(</span><span class="s2">&quot;        .$case&quot;</span><span class="p">)</span>
<span class="n">HK_WORKOUT_ACTIVITY_TYPE_EXTENSION_URL_TEMPLATE</span> <span class="o">=</span> <span class="n">Template</span><span class="p">(</span>
    <span class="s1">&#39;        case .$case: return Bundle.main.url(forResource: &quot;$resource&quot;, withExtension: &quot;$extension&quot;)&#39;</span>
<span class="p">)</span>


<span class="n">HK_WORKOUT_ACTIVITY_TYPE</span> <span class="o">=</span> <span class="s2">&quot;https://developer.apple.com/tutorials/data/documentation/healthkit/hkworkoutactivitytype.json&quot;</span>
<span class="n">WORKOUT_ASSET_SUFFIXES</span> <span class="o">=</span> <span class="p">[</span>
    <span class="s2">&quot;_112_Normal@3x.png&quot;</span><span class="p">,</span>
    <span class="s2">&quot;_112px-1_Normal@3x.png&quot;</span><span class="p">,</span>
    <span class="s2">&quot;_112px_Normal@3x.png&quot;</span><span class="p">,</span>
<span class="p">]</span>
<span class="n">WORKOUT_ASSETS_BASE_PATH</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&quot;assets/all/FitnessUI/&quot;</span><span class="p">)</span>
<span class="n">WORKOUT_ASSETS_OUTPUT_PATH</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&quot;assets/reduced/FitnessUI/&quot;</span><span class="p">)</span>
<span class="n">WORKOUT_ASSETS</span> <span class="o">=</span> <span class="n">chain</span><span class="p">(</span>
    <span class="o">*</span><span class="p">[</span>
        <span class="n">WORKOUT_ASSETS_BASE_PATH</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;*</span><span class="si">{</span><span class="n">pattern</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">WORKOUT_ASSET_SUFFIXES</span>
    <span class="p">]</span>
<span class="p">)</span>
<span class="n">WORKOUT_ASSET_LOOKUP</span> <span class="o">=</span> <span class="p">{</span>
    <span class="n">removesuffixes</span><span class="p">(</span><span class="n">asset</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="n">WORKOUT_ASSET_SUFFIXES</span><span class="p">):</span> <span class="n">asset</span>
    <span class="k">for</span> <span class="n">asset</span> <span class="ow">in</span> <span class="n">WORKOUT_ASSETS</span>
<span class="p">}</span>
<span class="n">WORKOUT_SPECIAL_CASES</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s2">&quot;australianFootball&quot;</span><span class="p">:</span> <span class="s2">&quot;australian_rules_football&quot;</span><span class="p">,</span>
    <span class="s2">&quot;cardioDance&quot;</span><span class="p">:</span> <span class="s2">&quot;dance&quot;</span><span class="p">,</span>
    <span class="s2">&quot;cycling&quot;</span><span class="p">:</span> <span class="s2">&quot;outdoorcycle&quot;</span><span class="p">,</span>
    <span class="s2">&quot;danceInspiredTraining&quot;</span><span class="p">:</span> <span class="s2">&quot;dance_insp_training&quot;</span><span class="p">,</span>
    <span class="s2">&quot;discSports&quot;</span><span class="p">:</span> <span class="s2">&quot;disk_sports&quot;</span><span class="p">,</span>
    <span class="s2">&quot;fitnessGaming&quot;</span><span class="p">:</span> <span class="s2">&quot;gaming_sports&quot;</span><span class="p">,</span>
    <span class="s2">&quot;functionalStrengthTraining&quot;</span><span class="p">:</span> <span class="s2">&quot;func_strength_training&quot;</span><span class="p">,</span>
    <span class="s2">&quot;highIntensityIntervalTraining&quot;</span><span class="p">:</span> <span class="s2">&quot;hiit&quot;</span><span class="p">,</span>
    <span class="s2">&quot;mixedCardio&quot;</span><span class="p">:</span> <span class="s2">&quot;mixed_meta_cardio_training&quot;</span><span class="p">,</span>
    <span class="s2">&quot;mixedMetabolicCardioTraining&quot;</span><span class="p">:</span> <span class="s2">&quot;mixed_meta_cardio_training&quot;</span><span class="p">,</span>
    <span class="s2">&quot;preparationAndRecovery&quot;</span><span class="p">:</span> <span class="s2">&quot;prep_and_recovery&quot;</span><span class="p">,</span>
    <span class="s2">&quot;running&quot;</span><span class="p">:</span> <span class="s2">&quot;outdoorrun&quot;</span><span class="p">,</span>
    <span class="s2">&quot;socialDance&quot;</span><span class="p">:</span> <span class="s2">&quot;social-dance&quot;</span><span class="p">,</span>
    <span class="s2">&quot;stairClimbing&quot;</span><span class="p">:</span> <span class="s2">&quot;stairs&quot;</span><span class="p">,</span>
    <span class="s2">&quot;surfingSports&quot;</span><span class="p">:</span> <span class="s2">&quot;surfing&quot;</span><span class="p">,</span>
    <span class="s2">&quot;swimming&quot;</span><span class="p">:</span> <span class="s2">&quot;swimopen&quot;</span><span class="p">,</span>
    <span class="s2">&quot;traditionalStrengthTraining&quot;</span><span class="p">:</span> <span class="s2">&quot;trad_weight_training&quot;</span><span class="p">,</span>
    <span class="s2">&quot;walking&quot;</span><span class="p">:</span> <span class="s2">&quot;outdoorwalk&quot;</span><span class="p">,</span>
    <span class="s2">&quot;wheelchairRunPace&quot;</span><span class="p">:</span> <span class="s2">&quot;wheelchairrun&quot;</span><span class="p">,</span>
    <span class="s2">&quot;wheelchairWalkPace&quot;</span><span class="p">:</span> <span class="s2">&quot;wheelchairwalk&quot;</span><span class="p">,</span>
<span class="p">}</span>

<span class="nd">@dataclass</span>
<span class="k">class</span><span class="w"> </span><span class="nc">Workout</span><span class="p">:</span>
    <span class="n">case</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">asset_path</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Path</span><span class="p">]</span>

    <span class="nd">@property</span>
    <span class="k">def</span><span class="w"> </span><span class="nf">humanized</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">Workout</span><span class="o">.</span><span class="n">humanize_by_case</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">case</span><span class="p">)</span>

    <span class="nd">@staticmethod</span>
    <span class="k">def</span><span class="w"> </span><span class="nf">match_asset_by_case</span><span class="p">(</span><span class="n">case</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Path</span><span class="p">]:</span>
        <span class="n">case_override</span> <span class="o">=</span> <span class="p">(</span>
            <span class="n">case</span> <span class="k">if</span> <span class="n">case</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">WORKOUT_SPECIAL_CASES</span> <span class="k">else</span> <span class="n">WORKOUT_SPECIAL_CASES</span><span class="p">[</span><span class="n">case</span><span class="p">]</span>
        <span class="p">)</span>

        <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="p">(</span>
            <span class="n">case_override</span><span class="p">,</span>
            <span class="n">underscore</span><span class="p">(</span><span class="n">case_override</span><span class="p">),</span>
            <span class="n">underscore</span><span class="p">(</span><span class="n">case_override</span><span class="p">)</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot;_&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">),</span>
        <span class="p">):</span>
            <span class="k">if</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">WORKOUT_ASSET_LOOKUP</span><span class="p">:</span>
                <span class="k">return</span> <span class="n">WORKOUT_ASSET_LOOKUP</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>

    <span class="nd">@staticmethod</span>
    <span class="k">def</span><span class="w"> </span><span class="nf">humanize_by_case</span><span class="p">(</span><span class="n">case</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">titleize</span><span class="p">(</span><span class="n">underscore</span><span class="p">(</span><span class="n">case</span><span class="p">))</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&quot;And&quot;</span><span class="p">,</span> <span class="s2">&quot;and&quot;</span><span class="p">)</span>

    <span class="nd">@staticmethod</span>
    <span class="k">def</span><span class="w"> </span><span class="nf">from_enum_case</span><span class="p">(</span><span class="n">case</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">Workout</span><span class="p">(</span><span class="n">case</span><span class="p">,</span> <span class="n">Workout</span><span class="o">.</span><span class="n">match_asset_by_case</span><span class="p">(</span><span class="n">case</span><span class="p">))</span>

    <span class="nd">@property</span>
    <span class="k">def</span><span class="w"> </span><span class="nf">template_mapping</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="p">{</span>
            <span class="s2">&quot;case&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">case</span><span class="p">,</span>
            <span class="s2">&quot;humanized&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">humanized</span><span class="p">,</span>
            <span class="s2">&quot;resource&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">asset_path</span><span class="o">.</span><span class="n">stem</span><span class="p">,</span>
            <span class="s2">&quot;extension&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">asset_path</span><span class="o">.</span><span class="n">suffix</span><span class="o">.</span><span class="n">removeprefix</span><span class="p">(</span><span class="s2">&quot;.&quot;</span><span class="p">),</span>
        <span class="p">}</span>


<span class="n">r</span> <span class="o">=</span> <span class="n">get</span><span class="p">(</span><span class="n">HK_WORKOUT_ACTIVITY_TYPE</span><span class="p">)</span>
<span class="n">json</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>


<span class="c1"># Only include non-deprecated workouts:</span>
<span class="c1"># references.* | [?kind==`symbol`&amp;&amp;deprecated==null].fragments[?[0].text==`case `][1].text</span>
<span class="n">workouts_variants</span> <span class="o">=</span> <span class="n">search</span><span class="p">(</span>
    <span class="s2">&quot;references.* | [?kind==`symbol`].fragments[?[0].text==`case `][1].text&quot;</span><span class="p">,</span> <span class="n">json</span>
<span class="p">)</span>
<span class="n">workouts</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span>
    <span class="p">[</span><span class="n">Workout</span><span class="o">.</span><span class="n">from_enum_case</span><span class="p">(</span><span class="n">workout</span><span class="p">)</span> <span class="k">for</span> <span class="n">workout</span> <span class="ow">in</span> <span class="n">workouts_variants</span><span class="p">],</span>
    <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">workout</span><span class="p">:</span> <span class="n">workout</span><span class="o">.</span><span class="n">case</span><span class="p">,</span>
<span class="p">)</span>

<span class="c1"># Only copy resources that are referenced by workouts</span>
<span class="k">for</span> <span class="n">workout</span> <span class="ow">in</span> <span class="n">workouts</span><span class="p">:</span>
    <span class="n">asset_path</span> <span class="o">=</span> <span class="n">workout</span><span class="o">.</span><span class="n">asset_path</span>
    <span class="k">if</span> <span class="n">asset_path</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
        <span class="n">copy</span><span class="p">(</span><span class="n">asset_path</span><span class="p">,</span> <span class="n">WORKOUT_ASSETS_OUTPUT_PATH</span><span class="p">)</span>

<span class="nb">print</span><span class="p">(</span>
    <span class="n">HK_WORKOUT_ACTIVITY_TYPE_EXTENSION_TEMPLATE</span><span class="o">.</span><span class="n">safe_substitute</span><span class="p">(</span>
        <span class="n">name</span><span class="o">=</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
            <span class="p">(</span>
                <span class="n">HK_WORKOUT_ACTIVITY_TYPE_EXTENSION_NAME_TEMPLATE</span><span class="o">.</span><span class="n">substitute</span><span class="p">(</span>
                    <span class="n">workout</span><span class="o">.</span><span class="n">template_mapping</span>
                <span class="p">)</span>
                <span class="k">for</span> <span class="n">workout</span> <span class="ow">in</span> <span class="n">workouts</span>
            <span class="p">)</span>
        <span class="p">),</span>
        <span class="n">all_cases</span><span class="o">=</span><span class="s2">&quot;,</span><span class="se">\n</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
            <span class="p">(</span>
                <span class="n">HK_WORKOUT_ACTIVITY_TYPE_EXTENSION_ALL_CASES_TEMPLATE</span><span class="o">.</span><span class="n">substitute</span><span class="p">(</span>
                    <span class="n">workout</span><span class="o">.</span><span class="n">template_mapping</span>
                <span class="p">)</span>
                <span class="k">for</span> <span class="n">workout</span> <span class="ow">in</span> <span class="n">workouts</span>
            <span class="p">)</span>
        <span class="p">),</span>
        <span class="n">url</span><span class="o">=</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
            <span class="p">(</span>
                <span class="n">HK_WORKOUT_ACTIVITY_TYPE_EXTENSION_URL_TEMPLATE</span><span class="o">.</span><span class="n">substitute</span><span class="p">(</span>
                    <span class="n">workout</span><span class="o">.</span><span class="n">template_mapping</span>
                <span class="p">)</span>
                <span class="k">for</span> <span class="n">workout</span> <span class="ow">in</span> <span class="n">workouts</span>
            <span class="p">)</span>
        <span class="p">),</span>
    <span class="p">)</span>
<span class="p">)</span>
</code></pre></div>


<figcaption><code>script.py</code></figcaption>


<div class="highlight"><pre><span></span><code><span class="n">httpx</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;^0.16.1&quot;</span>
<span class="n">jmespath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;^0.10.0&quot;</span>
<span class="n">inflection</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;^0.5.1&quot;</span>
</code></pre></div>


<figcaption><code>pyproject.toml</code> excerpt</figcaption>

</details>]]></content:encoded><guid isPermaLink="true">https://philip-trauner.me/blog/post/fitness-ui-icons</guid></item><item><title>Pedantic Review: Elgato Thunderbolt 3 Pro Dock</title><link>https://philip-trauner.me/blog/post/pedantic-elgato-thunderbolt-3-pro-dock-review</link><description>Overly pedantic review of Elgato's Thunderbolt 3 Pro Dock.</description><author>philip.trauner@arztpraxis.io</author><pubDate>Thu, 30 May 2019 12:54:20 GMT</pubDate><category>pedantic</category><category>review</category><category>elgato</category><category>thunderbolt</category><category>usb-c</category><category>dock</category><category>pedantic</category><content:encoded><![CDATA[<p align="center">
    <img src="/static/blog/post/pedantic-elgato-thunderbolt-3-pro-dock-review/content/dock.png" width="500px" style="margin-top: 50px;"/>
</p>
<figcaption>Elgato Thunderbolt 3 Pro Dock</figcaption>

<blockquote>
<p>Every once in a while, a revolutionary product comes along, that rids its users of some annoying little time-waste that stands between them and productivity.</p>
<p>— Steve Jobs (possibly altered to fit the narrative of this review)</p>
</blockquote>
<p>In my mind, Thunderbolt 3 docks are one such kind of product. Plug in <em>one</em> cable, and immediately pick up where you left off... that's the concept, at least.</p>
<p>Let's see if the <a href="https://www.elgato.com/de/dock/thunderbolt-3-pro">Elgato Thunderbolt 3 Pro Dock</a> delivers on that promise.</p>
<h2>Design</h2>
<p>The dock looks and feels premium. It has some heft to it (it weighs roughly half a kilogram), so it won't easily slide off ones desk because of stiff cabling and such. The  aluminum chassis is aesthetically pleasing and the whole device looks <em>very</em> similar to the back of a Mac Mini (which is good).</p>
<h2>I/0</h2>
<ul>
<li>Back<ul>
<li>Gigabit Ethernet</li>
<li>3.5mm headphone jack (line out)</li>
<li>2 x USB 3.1 Gen 2 Type C connector (10GB/s; 1.5A)</li>
<li>2 x Thunderbolt 3</li>
<li>DisplayPort 1.2</li>
</ul>
</li>
</ul>
<p align="center">
    <img src="/static/blog/post/pedantic-elgato-thunderbolt-3-pro-dock-review/content/back.png" width="500px"/>
</p>
<figcaption>Ports on the back of the dock</figcaption>

<ul>
<li>Front<ul>
<li>SD card reader</li>
<li>Micro SD card reader</li>
<li>3.5mm headphone jack (line in / out)</li>
<li>2 x USB 3.1 Gen 2 Type A connector (5GB/s; 1.5A)</li>
</ul>
</li>
</ul>
<p>The port selection is overall solid, but a second DP port certainly wouldn't have hurt, as there's only 2 Thunderbolt 3 ports, one of which will always be occupied by the connected computer.</p>
<h2>Price</h2>
<p>All Thunderbolt 3 docks are pretty expensive, and this one is no exception coming in at around <b style="color: #B12704;">350 USD / EUR</b>, but it still manages to be the most expensive out of the bunch. This can largely be attributed to the fact that Elgato has a very specific target audience in mind: <strong>Apple customers</strong>.</p>
<p>That's not just an assumption though, as basically all their marketing material only depicts Apple hardware, and the fact that they do not provide a Windows version of their <em>Thunderbolt Dock Utility</em>.</p>
<h2>Software</h2>
<p>The Dock Utility provides two optional pieces of functionality:</p>
<ol>
<li>High-power USB support</li>
<li>An eject all menu bar application (for storage devices attached to the dock)</li>
</ol>
<p>High-power USB support is achieved through two kernel modules (<code>ElgatoThunderbolt2DockChargingSupport.kext</code>, <code>ElgatoThunderboltDockChargingSupport.kext</code>) which basically tell <code>IOKit</code> that 15 Watts are available instead of 5.</p>
<p>A third module is thrown into the mix (<code>ElgatoThunderboltDockAudioRename.kext</code>) that updates the internal name of the USB audio devices that the dock exposes (to <code>Elgato Thunderbolt Dock Audio</code> / <code>Elgato Thunderbolt 2 Dock Audio</code>).</p>
<p>If neither feature is desired there is no point in installing the Dock Utility, as blindly installing kernel extensions of dubious quality is a great way to cause system instability.</p>
<h2>Audio quality</h2>
<blockquote>
<p>No setup is complete without epic sound. Thanks to a maximum sample rate of 96 kHz, and sample size of 24 bits, [...] you can plug in your high-fidelity headset without disconnecting your desktop speakers.</p>
<p>— Elgato marketing blurb</p>
</blockquote>
<p><strong>Fecal matter of male cattle</strong>. <em>Both</em> 3.5mm socket emit a quiet but still incredibly annoying static hiss. The fact that an Apple USB-C to 3.5mm headphone jack adapter produces better results is honestly staggering.<br>
That being said, its only really problematic for headphones, as the hiss practically isn't noticeable on most speakers, ... which doesn't change the fact that such poor audio quality is simply <em>not</em> acceptable.</p>
<h1>Conclusion</h1>
<p>Its alright, but certainly not worth the asking price. I won't be returning my unit, as its basically the only good-looking Thunderbolt 3 dock out there, and because a USB-C to 3.5mm headphone jack adapter sufficiently solves the audio quality issues.</p>
<p><strong>tldr</strong>: If design is the deciding factor, go with this one. If it isn't then the <a href="https://www.caldigit.com/ts3-plus">CalDigit TS3 Plus</a> is probably a better investment.</p>]]></content:encoded><guid isPermaLink="true">https://philip-trauner.me/blog/post/pedantic-elgato-thunderbolt-3-pro-dock-review</guid></item><item><title>Developer Diary Excerpts: machinectl auto-login</title><link>https://philip-trauner.me/blog/post/machinectl-autologin</link><description>Circumventing manual password input for fun and profit!</description><author>philip.trauner@arztpraxis.io</author><pubDate>Sat, 16 Mar 2019 16:54:32 GMT</pubDate><category>developer</category><category>diary</category><category>machinectl</category><category>systemd</category><category>login</category><category>autologin</category><category>getty</category><category>override</category><content:encoded><![CDATA[<p>Bringing up a root filesystem with <code>systemd-nspawn -b -D</code> and consequently logging into the container necessitates that either a password is set for the desired user, or that an <a href="https://wiki.archlinux.org/index.php/Getty#Nspawn_console"><code>autologin</code> override is present for the <code>console-getty</code> service</a>. </p>
<p>If a container is brought up with <code>machinectl start</code> instead, the <code>console-getty</code> override has no effect for consequent <code>machinectl login</code> invocations, as <code>container-getty@.service</code> is in charge of the pseudo terminals that <code>login</code> attaches to instead.</p>
<div class="highlight"><pre><span></span><code>    State: running
     Jobs: 0 queued
   Failed: 0 units
    Since: Sat 2019-03-16 17:11:26 CET; 16min ago
   CGroup: /
           ├─init.scope
           │ └─1 /lib/systemd/systemd
           └─system.slice
             ├─console-getty.service
             │ └─48 /sbin/agetty --noclear --keep-baud console 115200,38400,9600 vt220
             └─system-container\x2dgetty.slice
               └─container-getty@0.service
                 ├─53 /bin/login -f
                 └─58 -bash
</code></pre></div>

<figcaption>Shortened <code>systemctl status</code> output of container.</figcaption>

<p>Logging into the container without a password can still be achieved by using the <a href="https://www.freedesktop.org/software/systemd/man/machinectl.html#shell%20%5B%5BNAME@%5DNAME%20%5BPATH%20%5BARGUMENTS%E2%80%A6%5D%5D%5D%20"><code>shell</code> subcommand</a> of <a href="https://github.com/systemd/systemd/pull/1022"><code>machinectl</code></a>, though the <code>login</code> subcommand remains the only option for Linux distributions that ship with older versions of <code>systemd</code>, which do not include <code>shell</code> (before <a href="https://github.com/systemd/systemd/releases/tag/v224">v224</a>).</p>
<p>An <code>ExecStart</code> override for <code>container-getty@.service</code> can be used to replicate the behaviour of <code>shell</code> with <code>login</code>.</p>
<div class="highlight"><pre><span></span><code>systemctl<span class="w"> </span>edit<span class="w"> </span>container-getty@.service
</code></pre></div>

<figcaption>Execute inside the container, not the host system (a text editor has to be installed).</figcaption>

<div class="highlight"><pre><span></span><code><span class="k">[Service]</span>
<span class="na">ExecStart</span><span class="o">=</span><span class="w"> </span>
<span class="na">ExecStart</span><span class="o">=</span><span class="s">-/sbin/agetty --noclear --autologin root --keep-baud pts/%I 115200,38400,9600 $TERM</span><span class="w"> </span>
</code></pre></div>

<ul>
<li>The empty space after the first <code>ExecStart</code> is there on purpose, as it tells <code>systemd</code> to clear the pre-existing <code>ExecStart</code> content.</li>
<li>Substituting <code>root</code> with another user name will instead enable automatic login for the specified user.</li>
</ul>
<h2>Notes</h2>
<ul>
<li><code>systemd</code> as well as <code>dbus</code> should be installed in the container root file-system, otherwise both <code>login</code> and <code>shell</code> will fail (<code>Failed to get shell PTY: Protocol error</code>).</li>
<li>The override doesn't necessarily have to be created with the <code>edit</code> subcommand of <code>systemd</code>. Creating and subsequenly populating the contents of <code>/etc/systemd/system/container-getty@.service.d/override.conf</code> also works just as well.</li>
</ul>]]></content:encoded><guid isPermaLink="true">https://philip-trauner.me/blog/post/machinectl-autologin</guid></item><item><title>Python Quirks: Implicit Return</title><link>https://philip-trauner.me/blog/post/python-quirks-implicit-return</link><description>Python function always have to return something, right? Well, its complicated...</description><author>philip.trauner@arztpraxis.io</author><pubDate>Sun, 24 Feb 2019 18:47:37 GMT</pubDate><category>python</category><category>quirk</category><category>implicit</category><category>return</category><category>none</category><category>internals</category><category>bytecode</category><category>hack</category><content:encoded><![CDATA[<p>In Python, functions <strong>always</strong> have to return something.</p>
<div class="highlight"><pre><span></span><code><span class="gp">&gt;&gt;&gt; </span><span class="k">def</span><span class="w"> </span><span class="nf">foo</span><span class="p">():</span>
<span class="gp">... </span>    <span class="k">pass</span>
<span class="gp">...</span>
<span class="gp">&gt;&gt;&gt; </span><span class="nb">print</span><span class="p">(</span><span class="n">foo</span><span class="p">())</span>
<span class="go">None</span>
</code></pre></div>

<p>To ensure that this is the case, instructions that are equivalent to a <code>return None</code> statement are <a href="https://github.com/python/cpython/blob/16323cb2c3d315e02637cebebdc5ff46be32ecdf/Python/compile.c#L5861">appended to the inner-most code block</a> by the bytecode compiler if no return statement is present.</p>
<div class="highlight"><pre><span></span><code><span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span><span class="w"> </span><span class="nn">dis</span><span class="w"> </span><span class="kn">import</span> <span class="n">dis</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">dis</span><span class="p">(</span><span class="n">foo</span><span class="p">)</span>
<span class="go">  2           0 LOAD_CONST               0 (None)</span>
<span class="go">              2 RETURN_VALUE</span>
</code></pre></div>

<figcaption>Disassembled <code>foo</code> function</figcaption>

<p>Enforcing this requirement on an individual function level instead of inside the <a href="https://github.com/python/cpython/blob/234531b4462b20d668762bd78406fd2ebab129c9/Python/ceval.c">evaluation main loop</a> necessitates that all functions follow this protocol. Let's explore what happens when they <strong>don't</strong>.</p>
<h2>📚 Incomprehensive overview of Python bytecode</h2>
<p>The bytecode of existing functions can be examined by looking at their <code>__code__.co_code</code> attribute. </p>
<div class="highlight"><pre><span></span><code><span class="gp">&gt;&gt;&gt; </span><span class="n">foo</span><span class="o">.</span><span class="vm">__code__</span><span class="o">.</span><span class="n">co_code</span>
<span class="go">b&#39;d\x00S\x00&#39;</span>
</code></pre></div>

<p>Each instruction is 2 bytes long (<code>BB</code>) and consists of an opcode and an optional argument value. <a href="https://docs.python.org/3/library/struct.html#struct.unpack"><code>struct.unpack</code></a> can be used to decode individual instructions, and a human readable representation can be obtained through <a href="https://docs.python.org/3.7/library/dis.html#dis.opmap"><code>dis.opmap</code></a> (re-export of the undocumented <code>opcode.opmap</code>), which maps the opcode names to their respective numerical values. Swapping key and value subsequently provides a numerical value to text mapping.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">struct</span><span class="w"> </span><span class="kn">import</span> <span class="n">unpack</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">dis</span><span class="w"> </span><span class="kn">import</span> <span class="n">opmap</span>

<span class="n">reverse_opmap</span> <span class="o">=</span> <span class="p">{</span><span class="n">v</span><span class="p">:</span> <span class="n">k</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">opmap</span><span class="o">.</span><span class="n">items</span><span class="p">()}</span>


<span class="k">def</span><span class="w"> </span><span class="nf">foo</span><span class="p">():</span>
    <span class="k">pass</span>


<span class="n">foo_code</span> <span class="o">=</span> <span class="n">foo</span><span class="o">.</span><span class="vm">__code__</span><span class="o">.</span><span class="n">co_code</span>
<span class="k">for</span> <span class="n">pos</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">foo_code</span><span class="p">),</span> <span class="mi">2</span><span class="p">):</span>
    <span class="n">inst</span> <span class="o">=</span> <span class="n">unpack</span><span class="p">(</span><span class="s2">&quot;BB&quot;</span><span class="p">,</span> <span class="n">foo_code</span><span class="p">[</span><span class="n">pos</span> <span class="p">:</span> <span class="n">pos</span> <span class="o">+</span> <span class="mi">2</span><span class="p">])</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">reverse_opmap</span><span class="p">[</span><span class="n">inst</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">inst</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
</code></pre></div>

<details open><summary>Output</summary>

<div class="highlight"><pre><span></span><code>LOAD_CONST: 0
RETURN_VALUE: 0
</code></pre></div>


</details>

<p>The bytecode section that instructs the VM to return <code>None</code> is 4 bytes long:  </p>
<ol>
<li>
<p><code>LOAD_CONST</code>(0)</p>
<p>Loads the constant stored at index 0 of <code>__code__.co_consts</code> (which is <code>None</code> in this case) into memory.  </p>
</li>
<li>
<p><code>RETURN_VALUE</code>(0)</p>
<p>Returns the previously loaded constant (argument-less).</p>
</li>
</ol>
<h2>🤞 Crossing fingers</h2>
<p>Stripping out the 4 bytes, replacing the <code>__code__</code> attribute of the <code>foo</code> function, and executing the resulting function does not crash the VM (surprisingly). Instead, a <a href="https://github.com/python/cpython/blob/11c79531655a4aa3f82c20ff562ac571f40040cc/Python/ceval.c#L3430"><code>SystemError</code> is raised</a> and an accompanying <a href="https://github.com/python/cpython/blob/11c79531655a4aa3f82c20ff562ac571f40040cc/Python/ceval.c#L3426">error message is printed to <code>stderr</code></a>.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">types</span><span class="w"> </span><span class="kn">import</span> <span class="n">FunctionType</span><span class="p">,</span> <span class="n">CodeType</span>


<span class="k">def</span><span class="w"> </span><span class="nf">foo</span><span class="p">():</span>
    <span class="k">pass</span>


<span class="n">foo_code</span> <span class="o">=</span> <span class="n">foo</span><span class="o">.</span><span class="vm">__code__</span>
<span class="n">foo</span><span class="o">.</span><span class="vm">__code__</span> <span class="o">=</span> <span class="n">CodeType</span><span class="p">(</span>
    <span class="n">foo_code</span><span class="o">.</span><span class="n">co_argcount</span><span class="p">,</span>
    <span class="n">foo_code</span><span class="o">.</span><span class="n">co_kwonlyargcount</span><span class="p">,</span>
    <span class="n">foo_code</span><span class="o">.</span><span class="n">co_nlocals</span><span class="p">,</span>
    <span class="n">foo_code</span><span class="o">.</span><span class="n">co_stacksize</span><span class="p">,</span>
    <span class="n">foo_code</span><span class="o">.</span><span class="n">co_flags</span><span class="p">,</span>
    <span class="n">foo_code</span><span class="o">.</span><span class="n">co_code</span><span class="p">[:</span><span class="o">-</span><span class="mi">4</span><span class="p">],</span>
    <span class="n">foo_code</span><span class="o">.</span><span class="n">co_consts</span><span class="p">,</span>
    <span class="n">foo_code</span><span class="o">.</span><span class="n">co_names</span><span class="p">,</span>
    <span class="n">foo_code</span><span class="o">.</span><span class="n">co_varnames</span><span class="p">,</span>
    <span class="s2">&quot;&quot;</span><span class="p">,</span>
    <span class="n">foo_code</span><span class="o">.</span><span class="n">co_name</span><span class="p">,</span>
    <span class="mi">1</span><span class="p">,</span>
    <span class="sa">b</span><span class="s2">&quot;&quot;</span><span class="p">,</span>
<span class="p">)</span>

<span class="n">foo</span><span class="p">()</span>
</code></pre></div>

<details open><summary>Output</summary>

<div class="highlight"><pre><span></span><code>XXX lineno: 1, opcode: 0
Traceback (most recent call last):
  File &quot;/Users/philip/Developer/testing/python-implicit-return.py&quot;, line 25, in &lt;module&gt;
    foo()
  File &quot;&quot;, line 1, in foo
SystemError: unknown opcode
</code></pre></div>


</details>

<p><code>SystemError</code> is a regular runtime error (despite its name), and can therefor be caught with a regular <code>try</code> / <code>except</code> clause.</p>
<div class="highlight"><pre><span></span><code><span class="gp">&gt;&gt;&gt; </span><span class="k">try</span><span class="p">:</span>
<span class="gp">... </span>    <span class="n">foo</span><span class="p">()</span>
<span class="gp">... </span><span class="k">except</span> <span class="ne">SystemError</span><span class="p">:</span>
<span class="gp">... </span>    <span class="k">pass</span>
</code></pre></div>

<p>The accompanying error message (<code>XXX lineno: 1, opcode: 0</code>) can't be silence from within Python, because the output stream to which it is written is hard-coded to <code>stderr</code>. That doesn't mean that its impossible, though.</p>
<div class="highlight"><pre><span></span><code>python3<span class="w"> </span>&lt;file&gt;<span class="w"> </span><span class="m">2</span>&gt;<span class="w"> </span>&gt;<span class="o">(</span>sed<span class="w"> </span><span class="s2">&quot;/^XXX lineno: [^,]*, opcode: [^\n]*/d&quot;</span><span class="w"> </span>&gt;<span class="p">&amp;</span><span class="m">2</span><span class="o">)</span>
</code></pre></div>

<h2>🏁 Conclusion</h2>
<p><strong>Don't</strong> do this.</p>]]></content:encoded><guid isPermaLink="true">https://philip-trauner.me/blog/post/python-quirks-implicit-return</guid></item><item><title>Python Tips: Generator unrolling</title><link>https://philip-trauner.me/blog/post/python-tips-fast-generator-unrolling</link><description>There are many ways to unroll generators in Python, but which one is the best?</description><author>philip.trauner@arztpraxis.io</author><pubDate>Sat, 26 Jan 2019 23:55:06 GMT</pubDate><category>python</category><category>tip</category><category>fast</category><category>generator</category><category>unrolling</category><category>performance</category><category>benchmark</category><content:encoded><![CDATA[<p>Generators are computed iterables, which only require a fraction of the space that would normally be necessary to store a fully populated collection in memory. </p>
<p>While this property is generally beneficial, they also bring along some ergonomic problems:</p>
<ul>
<li>No index operator support</li>
<li>Cloning is problematic (see <a href="https://docs.python.org/3/library/itertools.html#itertools.tee"><code>itertools.tee</code></a>)</li>
<li>Results have to be stored in an auxiliary collection if caching is desired</li>
<li>Debugging misbehaving generators is non-trivial</li>
</ul>
<p>The most common way to deal with these shortcomings is to just unroll the generator into a collection if the element count yielded by the generator is manageable. There are many ways to accomplish this, but all of them have different strengths and shortcomings.</p>
<details><summary>Benchmark code</summary>


<div class="highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">timeit</span><span class="w"> </span><span class="kn">import</span> <span class="n">timeit</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">functools</span><span class="w"> </span><span class="kn">import</span> <span class="n">partial</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">array</span><span class="w"> </span><span class="kn">import</span> <span class="n">array</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">sys</span><span class="w"> </span><span class="kn">import</span> <span class="n">getsizeof</span>

<span class="n">RUNS</span> <span class="o">=</span> <span class="mi">1000</span>
<span class="n">RANGE</span> <span class="o">=</span> <span class="mi">10000</span>

<span class="k">for</span> <span class="n">exprs</span><span class="p">,</span> <span class="n">desc</span> <span class="ow">in</span> <span class="p">(</span>
    <span class="p">(</span><span class="k">lambda</span> <span class="n">gen</span><span class="p">:</span> <span class="nb">list</span><span class="p">(</span><span class="n">gen</span><span class="p">),</span> <span class="s2">&quot;List constructor&quot;</span><span class="p">),</span>
    <span class="p">(</span>
        <span class="k">lambda</span> <span class="n">gen</span><span class="p">:</span> <span class="p">(</span><span class="k">lambda</span> <span class="n">gen</span><span class="p">,</span> <span class="nb">list</span><span class="p">:</span> <span class="p">[</span><span class="nb">list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">num</span><span class="p">)</span> <span class="k">for</span> <span class="n">num</span> <span class="ow">in</span> <span class="n">gen</span><span class="p">])(</span><span class="n">gen</span><span class="p">,</span> <span class="nb">list</span><span class="p">()),</span>
        <span class="s2">&quot;List append&quot;</span><span class="p">,</span>
    <span class="p">),</span>
    <span class="p">(</span><span class="k">lambda</span> <span class="n">gen</span><span class="p">:</span> <span class="p">[</span><span class="n">num</span> <span class="k">for</span> <span class="n">num</span> <span class="ow">in</span> <span class="n">gen</span><span class="p">],</span> <span class="s2">&quot;List comprehension&quot;</span><span class="p">),</span>
    <span class="p">(</span><span class="k">lambda</span> <span class="n">gen</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">(</span><span class="n">gen</span><span class="p">),</span> <span class="s2">&quot;Tuple constructor&quot;</span><span class="p">),</span>
    <span class="p">(</span><span class="k">lambda</span> <span class="n">gen</span><span class="p">:</span> <span class="nb">set</span><span class="p">(</span><span class="n">gen</span><span class="p">),</span> <span class="s2">&quot;Set constructor&quot;</span><span class="p">),</span>
    <span class="p">(</span><span class="k">lambda</span> <span class="n">gen</span><span class="p">:</span> <span class="nb">frozenset</span><span class="p">(</span><span class="n">gen</span><span class="p">),</span> <span class="s2">&quot;Frozenset constructor&quot;</span><span class="p">),</span>
    <span class="p">(</span><span class="k">lambda</span> <span class="n">gen</span><span class="p">:</span> <span class="p">{</span><span class="n">num</span><span class="p">:</span> <span class="kc">None</span> <span class="k">for</span> <span class="n">num</span> <span class="ow">in</span> <span class="n">gen</span><span class="p">},</span> <span class="s2">&quot;Dictionary nonsense&quot;</span><span class="p">),</span>
    <span class="p">(</span><span class="k">lambda</span> <span class="n">gen</span><span class="p">:</span> <span class="n">array</span><span class="p">(</span><span class="s2">&quot;h&quot;</span><span class="p">,</span> <span class="n">gen</span><span class="p">),</span> <span class="s2">&quot;Array constructor&quot;</span><span class="p">),</span>
    <span class="p">(</span>
        <span class="k">lambda</span> <span class="n">gen</span><span class="p">:</span> <span class="p">(</span><span class="k">lambda</span> <span class="n">gen</span><span class="p">,</span> <span class="n">arr</span><span class="p">:</span> <span class="n">arr</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">gen</span><span class="p">)</span> <span class="ow">or</span> <span class="n">arr</span><span class="p">)(</span><span class="n">gen</span><span class="p">,</span> <span class="n">array</span><span class="p">(</span><span class="s2">&quot;h&quot;</span><span class="p">)),</span>
        <span class="s2">&quot;Array extension&quot;</span><span class="p">,</span>
    <span class="p">),</span>
    <span class="p">(</span>
        <span class="k">lambda</span> <span class="n">gen</span><span class="p">:</span> <span class="p">(</span><span class="k">lambda</span> <span class="n">gen</span><span class="p">,</span> <span class="n">arr</span><span class="p">:</span> <span class="p">[</span><span class="n">arr</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">num</span><span class="p">)</span> <span class="k">for</span> <span class="n">num</span> <span class="ow">in</span> <span class="n">gen</span><span class="p">])(</span>
            <span class="n">gen</span><span class="p">,</span> <span class="n">array</span><span class="p">(</span><span class="s2">&quot;h&quot;</span><span class="p">)</span>
        <span class="p">),</span>
        <span class="s2">&quot;Array append&quot;</span><span class="p">,</span>
    <span class="p">),</span>
<span class="p">):</span>
    <span class="n">bound_expr</span> <span class="o">=</span> <span class="n">partial</span><span class="p">(</span><span class="n">exprs</span><span class="p">,</span> <span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="nb">range</span><span class="p">(</span><span class="n">RANGE</span><span class="p">))())</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">timeit</span><span class="p">(</span><span class="n">bound_expr</span><span class="p">,</span><span class="w"> </span><span class="n">number</span><span class="o">=</span><span class="n">RUNS</span><span class="p">)</span><span class="si">:</span><span class="s2">.5f</span><span class="si">}</span><span class="s2">s: </span><span class="si">{</span><span class="n">desc</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">bound_expr</span><span class="p">())</span><span class="si">}</span><span class="s2">)&quot;</span><span class="p">)</span>
</code></pre></div>



</details>

<table>
<thead>
<tr>
<th>Method</th>
<th>Description</th>
<th style="text-align: center;">Speed (sec)</th>
<th style="text-align: right;">Size (byte)</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>list(gen)</code></td>
<td>List constructor</td>
<td style="text-align: center;">0.27175</td>
<td style="text-align: right;">90112</td>
</tr>
<tr>
<td><code>tuple(gen)</code></td>
<td>Tuple constructor</td>
<td style="text-align: center;">0.35145</td>
<td style="text-align: right;">80048</td>
</tr>
<tr>
<td><code>frozenset(gen)</code></td>
<td>Frozenset constructor</td>
<td style="text-align: center;">0.47581</td>
<td style="text-align: right;">524512</td>
</tr>
<tr>
<td><code>set(gen)</code></td>
<td>Set constructor</td>
<td style="text-align: center;">0.47707</td>
<td style="text-align: right;">524512</td>
</tr>
<tr>
<td><code>[num for num in gen]</code></td>
<td>List comprehension</td>
<td style="text-align: center;">0.52155</td>
<td style="text-align: right;">87624</td>
</tr>
<tr>
<td><code>{num: None for num in gen}</code></td>
<td>Dictionary nonsense</td>
<td style="text-align: center;">0.74752</td>
<td style="text-align: right;">295008</td>
</tr>
<tr>
<td><code>array("h", gen)</code></td>
<td>Array constructor (<code>from array import array</code>)</td>
<td style="text-align: center;">0.90895</td>
<td style="text-align: right;">20234</td>
</tr>
<tr>
<td><code>arr.extend(gen)</code></td>
<td>Array extension</td>
<td style="text-align: center;">0.94037</td>
<td style="text-align: right;">20234</td>
</tr>
<tr>
<td><code>[list.append(num) for num in gen]</code></td>
<td>List append</td>
<td style="text-align: center;">1.19855</td>
<td style="text-align: right;">87624</td>
</tr>
<tr>
<td><code>[arr.append(num) for num in gen]</code></td>
<td>Array append</td>
<td style="text-align: center;">1.77882</td>
<td style="text-align: right;">87624</td>
</tr>
</tbody>
</table>
<h2>Conclusion</h2>
<ul>
<li>The list constructor is the definitive all-rounder and the best choice in most cases. </li>
<li>Typed arrays shine when memory usage is key.</li>
<li>The tuple constructor is about 20% slower than the list constructor, while taking up ~10% less space.</li>
</ul>]]></content:encoded><guid isPermaLink="true">https://philip-trauner.me/blog/post/python-tips-fast-generator-unrolling</guid></item><item><title>Python Tips: Dynamic function definition</title><link>https://philip-trauner.me/blog/post/python-tips-dynamic-function-definition</link><description>Function definition in Python can be considered static, right? Wrong!</description><author>philip.trauner@arztpraxis.io</author><pubDate>Thu, 10 Jan 2019 20:41:57 GMT</pubDate><category>python</category><category>tip</category><category>internal</category><category>dynamic</category><category>function</category><category>definition</category><category>pattern</category><category>cpython</category><content:encoded><![CDATA[<p>In Python, there isn't any syntactic sugar that eases function definition during runtime. However, that doesn't mean that its impossible or even difficult.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">types</span><span class="w"> </span><span class="kn">import</span> <span class="n">FunctionType</span>

<span class="n">foo_code</span> <span class="o">=</span> <span class="nb">compile</span><span class="p">(</span><span class="s1">&#39;def foo(): return &quot;bar&quot;&#39;</span><span class="p">,</span> <span class="s2">&quot;&lt;string&gt;&quot;</span><span class="p">,</span> <span class="s2">&quot;exec&quot;</span><span class="p">)</span>
<span class="n">foo_func</span> <span class="o">=</span> <span class="n">FunctionType</span><span class="p">(</span><span class="n">foo_code</span><span class="o">.</span><span class="n">co_consts</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nb">globals</span><span class="p">(),</span> <span class="s2">&quot;foo&quot;</span><span class="p">)</span>

<span class="nb">print</span><span class="p">(</span><span class="n">foo_func</span><span class="p">())</span>
</code></pre></div>

<details open><summary>Output</summary>

<div class="highlight"><pre><span></span><code>bar
</code></pre></div>


</details>

<h2>Dissection</h2>
<p>Stepping through the code line-by-line reveals how thin the language / interpreter barrier really is.</p>
<div class="highlight"><pre><span></span><code><span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span><span class="w"> </span><span class="nn">types</span><span class="w"> </span><span class="kn">import</span> <span class="n">FunctionType</span>
</code></pre></div>

<p>The Python docs often do not list the signatures of classes that aren't meant to be created manually (which is completely reasonable). <br>
There are three ways to work around this: <a href="https://docs.python.org/3.7/library/functions.html#help"><code>help()</code></a>, <a href="https://docs.python.org/3/library/inspect.html"><code>inspect</code></a> (which can't examine built-in functions), and the fallback solution of looking at the <a href="https://github.com/python/cpython/">CPython</a> source code. <br>
In this instance both <code>help()</code> and <code>inspect</code> do the job, but looking at the actual <a href="https://github.com/python/cpython/blob/5bb146aaea1484bcc117ab6cb38dda39ceb5df0f/Objects/funcobject.c#L458">source code</a> reveals additional details in regards to data-types.</p>
<div class="highlight"><pre><span></span><code><span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span><span class="w"> </span><span class="nn">inspect</span><span class="w"> </span><span class="kn">import</span> <span class="n">signature</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">signature</span><span class="p">(</span><span class="n">FunctionType</span><span class="p">)</span>
<span class="go">&lt;Signature (code, globals, name=None, argdefs=None, closure=None)&gt;</span>
</code></pre></div>

<ol>
<li><code>code</code><br>
    Internally a <a href="https://github.com/python/cpython/blob/master/Objects/codeobject.c"><code>PyCodeObject</code></a>, which is exposed as a <a href="https://docs.python.org/3.7/library/types.html#types.CodeType"><code>types.CodeType</code></a>. <br>
    Non-built-in functions have a <code>__code__</code> attribute which holds their corresponding code object. <br>
<a href="https://docs.python.org/3.7/library/types.html#types.CodeType"><code>types.CodeType</code></a> objects can be created at runtime by utilizing the <a href="https://docs.python.org/3/library/functions.html#compile"><code>compile()</code></a> built-in.</li>
<li><code>globals</code><br>
    If a variable that is being referenced in a function isn't defined locally, passed in as a parameter, provided by a default argument value, or supplied through a closure context, it is looked up in the <code>globals</code> dictionary. <br>
    The <code>globals()</code> built-in function returns a <strong>reference</strong> to the global symbol table of the current module, and can therefor be used to supply a dictionary that is always up-to-date with the current state of said table. Passing in any other dictionary works as well (<code>FunctionType((lambda: bar).__code__, {"bar" : "baz"}, "foo")() == "baz"</code>).</li>
<li><code>name</code> (optional)<br>
    Controls the <code>__name__</code> attribute of the returned function. Only really useful for lambdas (due to their anonymous nature they normally don't have names), and renaming functions.</li>
<li><code>argdefs</code> (optional)<br>
    Provides a way to supply default argument values (<code>def foo(bar="baz")</code>) by passing in a <a href="https://docs.python.org/3.7/library/stdtypes.html#tuple"><code>tuple</code></a> that contains objects of arbitrary type. (<code>FunctionType((lambda bar: bar).__code__, {}, "foo", (10,))() == 10</code>).  </li>
<li><code>closure</code> (optional)<br>
    (Probably shouldn't be touched if execution in any Python VM other than CPython (PyPy, Jython, ...) is desired, as it heavily relies on implementation details)<br>
    A <code>tuple</code> of <a href="https://github.com/python/cpython/blob/master/Objects/cellobject.c"><code>cell</code></a> objects. <a href="https://github.com/PhilipTrauner/exalt/blob/846763f24c9e09e578ea24216f08e4268eb71bc0/exalt/__init__.py#L43">Creating</a> <code>cell</code> objects isn't exactly straight forward, because calling into <a href="https://github.com/python/cpython/blob/5bb146aaea1484bcc117ab6cb38dda39ceb5df0f/Objects/cellobject.c#L9">CPython internals</a> is required, but there is a library that makes this more convenient: <a href="https://github.com/PhilipTrauner/exalt">exalt</a> (shameless advertisement). </li>
</ol>
<div class="highlight"><pre><span></span><code><span class="gp">&gt;&gt;&gt; </span><span class="n">foo_code</span> <span class="o">=</span> <span class="nb">compile</span><span class="p">(</span><span class="s1">&#39;def foo(): return &quot;bar&quot;&#39;</span><span class="p">,</span> <span class="s2">&quot;&lt;string&gt;&quot;</span><span class="p">,</span> <span class="s2">&quot;exec&quot;</span><span class="p">)</span>
</code></pre></div>

<p><code>compile()</code> is a built-in function, and therefor also well <a href="https://docs.python.org/3.7/library/functions.html#compile">documented</a>. </p>
<p><code>exec</code> mode is utilized, because multiple statements are necessary to define a function.</p>
<div class="highlight"><pre><span></span><code><span class="gp">&gt;&gt;&gt; </span><span class="n">foo_func</span> <span class="o">=</span> <span class="n">FunctionType</span><span class="p">(</span><span class="n">foo_code</span><span class="o">.</span><span class="n">co_consts</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nb">globals</span><span class="p">(),</span> <span class="s2">&quot;foo&quot;</span><span class="p">)</span>
</code></pre></div>

<p>Bringing it all together and assigning the dynamically created function to a variable. <br>
The function that was compiled in the previous statement becomes the first constant of the generated code object, therefor just referring to <code>foo_code</code> is insufficient. This is a direct consequence of <code>exec</code> mode, because the resulting code object can contain multiple constants.  </p>
<div class="highlight"><pre><span></span><code><span class="gp">&gt;&gt;&gt; </span><span class="nb">print</span><span class="p">(</span><span class="n">foo_func</span><span class="p">())</span>
</code></pre></div>

<p>The dynamically created function can be called just like any other function.</p>
<h2>Conclusion</h2>
<ul>
<li>There are very few use-cases for dynamic function creation outside of experimentation.</li>
<li>Toying around with Python internals is a great way to learn more about the language.</li>
<li>The interpreter / language boundary can be crossed rather effortlessly if desired.</li>
</ul>
<p>And as always: <strong>Don’t abuse the language</strong> (well, a little can’t hurt, right?)</p>]]></content:encoded><guid isPermaLink="true">https://philip-trauner.me/blog/post/python-tips-dynamic-function-definition</guid></item><item><title>Python Quirks: Lambdas</title><link>https://philip-trauner.me/blog/post/python-quirks-lambdas</link><description>Python lambdas are weird and underpowered... But why?</description><author>philip.trauner@arztpraxis.io</author><pubDate>Wed, 26 Dec 2018 14:25:03 GMT</pubDate><category>python</category><category>quirk</category><category>lambda</category><category>pattern</category><category>binding</category><content:encoded><![CDATA[<blockquote>
<p>The crippled nature of Python's lambdas are a big weakness of Python. They sit in a no-mans land of providing half a solution but being too crippled to actually <del>to</del> <em>improve</em> clarity / <del>improve</del> programming style.</p>
<p>— <a href="https://news.ycombinator.com/item?id=18089917">zmmmmm</a></p>
</blockquote>
<p>Comments like this one about Python's lambdas are <a href="http://treyhunner.com/2018/09/stop-writing-lambda-expressions/">very</a>, <a href="http://web.archive.org/web/20140711172717/http://symbo1ics.com/blog/?p=1292">very</a>, <a href="https://news.ycombinator.com/item?id=18088758">very</a> common. Even Guido himself isn't particularly fond of lambdas, as demonstrated by his suggestion to <a href="https://www.artima.com/weblogs/viewpost.jsp?thread=98196">remove them</a> outright.</p>
<p>With all the negativity surrounding this admittedly underpowered language feature it is worth exploring why the capabilities of lambdas are so limited, and how to make use of them.</p>
<h2>History lesson</h2>
<blockquote>
<p>About 12 years ago, Python <del>aquired</del> acquired lambda, reduce(), filter() and map(), courtesy of (I believe) a Lisp hacker who missed them and submitted working patches. </p>
<p>— <a href="https://www.artima.com/weblogs/viewpost.jsp?thread=98196">Guido van Rossum</a> (2005)</p>
</blockquote>
<p><code>lambda_input</code> was first introduced into the grammar on <a href="https://github.com/python/cpython/blob/12d12c5faf4d770160b7975b54e8f9b12694e012/Grammar/Grammar#L88">October 26th 1993</a>. At this point <code>lambda</code> wasn't a keyword yet, but instead a function that would consume a <code>vararglist</code> (parameter list) and <code>testlist</code> (basically an expression) as a string. </p>
<div class="highlight"><pre><span></span><code><span class="n">lambd</span><span class="err">​</span><span class="n">a</span><span class="p">(</span><span class="s1">&#39;x: x + 1&#39;</span><span class="p">)</span>
</code></pre></div>

<p>About <a href="https://github.com/python/cpython/blob/590baa4a7a43b596119b47f605e3e570c2b3b0ee/Grammar/Grammar#L141">one month</a> later it finally became a keyword and has remained largely unchanged to this day.</p>
<h2>Pythonic?</h2>
<blockquote>
<p>For example, today someone claimed to have solved the problem of the multi-statement lambda.<br>
But such solutions often lack "Pythonicity" [...]  </p>
<p>— <a href="https://www.artima.com/weblogs/viewpost.jsp?thread=147358">Guido van Rossum</a> (2006)</p>
</blockquote>
<p>Technical feasibility is not really a concern when it comes to possible lambda improvements. The main issue is that Guido will not accept a solution that introduces indentation within expressions on the grounds of lacking "Pythonicity". This effectively rules out multi-statement lambdas.</p>
<div class="highlight"><pre><span></span><code><span class="n">foo</span><span class="p">(</span>
    <span class="p">(</span>
        <span class="k">lambda</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span>
            <span class="nb">print</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
            <span class="nb">print</span><span class="p">(</span><span class="n">b</span><span class="p">)</span>
    <span class="p">),</span> 
    <span class="s2">&quot;bar&quot;</span><span class="p">,</span>
<span class="p">)</span> 
</code></pre></div>

<figcaption>Possible sparse syntax for multi-statement lambdas. Statements are enclosed by parentheses and indented one level further than the declaration.</figcaption>

<div class="highlight"><pre><span></span><code><span class="n">foo</span><span class="p">((</span>
    <span class="k">lambda</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
        <span class="nb">print</span><span class="p">(</span><span class="n">b</span><span class="p">)</span>
    <span class="p">),</span> 
    <span class="s2">&quot;bar&quot;</span><span class="p">,</span>
<span class="p">)</span> 
</code></pre></div>

<figcaption>Denser syntax for multi-statement lambdas.</figcaption>

<p>If both variants were to be deemed valid, the concept of significant whitespace for indentation would be defeated, because the sparse syntax would require an optional extra level of indentation. If only one variant was to be allowed, there would be endless bike-shedding over which one should be chosen.</p>
<h2>Patterns</h2>
<h3>Function binding</h3>
<div class="highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">functools</span><span class="w"> </span><span class="kn">import</span> <span class="n">partial</span>

<span class="k">for</span> <span class="n">fun</span> <span class="ow">in</span> <span class="p">(</span><span class="n">partial</span><span class="p">(</span><span class="k">lambda</span> <span class="n">number</span><span class="p">,</span> <span class="n">exp</span><span class="p">:</span> <span class="n">number</span> <span class="o">**</span> <span class="n">exp</span><span class="p">,</span> <span class="n">number</span><span class="p">)</span> <span class="k">for</span> <span class="n">number</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">RUNS</span><span class="p">)):</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">fun</span><span class="p">(</span><span class="mi">2</span><span class="p">))</span>
</code></pre></div>

<p>(Nested) lambdas can be used instead of <code>partial</code> for binding values to functions.</p>
<div class="highlight"><pre><span></span><code><span class="k">for</span> <span class="n">fun</span> <span class="ow">in</span> <span class="p">((</span><span class="k">lambda</span> <span class="n">number</span><span class="p">:</span> <span class="k">lambda</span> <span class="n">exp</span><span class="p">:</span> <span class="n">number</span> <span class="o">**</span> <span class="n">exp</span><span class="p">)(</span><span class="n">number</span><span class="p">)</span> <span class="k">for</span> <span class="n">number</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">RUNS</span><span class="p">)):</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">fun</span><span class="p">(</span><span class="mi">2</span><span class="p">))</span>
</code></pre></div>

<p>Readability is questionable is both cases, but benchmarking (10^6*5 runs) reveals some noticeable speed improvements when using list comprehensions, which are eagerly evaluated. Generator expressions, which evaluate lazily, exhibit a smaller performance gain. The overhead of lambda binding is generally smaller, because a wrapper function is created instead of a <a href="https://docs.python.org/3.7/library/functools.html#partial-objects"><code>partial</code> object</a>.</p>
<table style="display: table !important;">
<tr>
    <th>Eager</th>
    <th>Lazy</th>
</tr>
<tr>
<td>
    <table style="display: table !important;">
        <thead>
            <tr>
                <th><strong>Partial</strong></th>
                <th><strong>Nested</strong></th>
                <th><strong>Improvement</strong></th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>12.097278s</td>
                <td>11.102859s</td>
                <td>8,22%</td>
            </tr>
            <tr>
                <td>12.075038s</td>
                <td>10.530266s</td>
                <td>12,80%</td>
            </tr>
            <tr>
                <td>11.61805s</td>
                <td>10.08913s</td>
                <td>13,16%</td>
            </tr>
        </tbody>
    </table>
</td>
<td>
    <table style="display: table !important;">
        <thead>
            <tr>
                <th><strong>Partial</strong></th>
                <th><strong>Nested</strong></th>
                <th><strong>Improvement</strong></th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>5.131468s</td>
                <td>4.854471s</td>
                <td>5,40%</td>
            </tr>
            <tr>
                <td>4.223810s</td>
                <td>4.122884s</td>
                <td>2,39%</td>
            </tr>
            <tr>
                <td>4.248887s</td>
                <td>4.158348s</td>
                <td>2,13%</td>
            </tr>
        </tbody>
    </table>
</td>
</table>

<details><summary>Benchmark code</summary>


<div class="highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">functools</span><span class="w"> </span><span class="kn">import</span> <span class="n">partial</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">time</span><span class="w"> </span><span class="kn">import</span> <span class="n">time</span>

<span class="n">ENTRIES</span> <span class="o">=</span> <span class="mi">5000000</span>
<span class="n">RUNS</span> <span class="o">=</span> <span class="mi">2</span>
<span class="n">FUNCTION_ARG</span> <span class="o">=</span> <span class="p">(</span><span class="mi">2</span><span class="p">,)</span>


<span class="k">def</span><span class="w"> </span><span class="nf">bench</span><span class="p">(</span><span class="n">generator_expression</span><span class="p">,</span> <span class="n">expand</span><span class="p">):</span>
    <span class="n">start_bind</span> <span class="o">=</span> <span class="n">time</span><span class="p">()</span>
    <span class="n">callables_</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">generator_expression</span><span class="p">)</span> <span class="k">if</span> <span class="n">expand</span> <span class="k">else</span> <span class="n">generator_expression</span>
    <span class="n">end_bind</span> <span class="o">=</span> <span class="n">time</span><span class="p">()</span>

    <span class="n">start_exec</span> <span class="o">=</span> <span class="n">time</span><span class="p">()</span>

    <span class="k">for</span> <span class="n">fun</span> <span class="ow">in</span> <span class="n">callables_</span><span class="p">:</span>
        <span class="n">fun</span><span class="p">(</span><span class="o">*</span><span class="n">FUNCTION_ARG</span><span class="p">)</span>

    <span class="n">end_exec</span> <span class="o">=</span> <span class="n">time</span><span class="p">()</span>

    <span class="k">return</span> <span class="p">(</span><span class="n">end_bind</span> <span class="o">-</span> <span class="n">start_bind</span><span class="p">)</span> <span class="k">if</span> <span class="n">expand</span> <span class="k">else</span> <span class="kc">None</span><span class="p">,</span> <span class="p">(</span><span class="n">end_exec</span> <span class="o">-</span> <span class="n">start_exec</span><span class="p">)</span>


<span class="k">def</span><span class="w"> </span><span class="nf">format_result</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="n">expand</span><span class="p">):</span>
    <span class="k">return</span> <span class="s2">&quot;</span><span class="si">%s</span><span class="s2">Executing time: </span><span class="si">%f</span><span class="s2">s</span><span class="se">\n</span><span class="s2">Overall time: </span><span class="si">%f</span><span class="s2">s&quot;</span> <span class="o">%</span> <span class="p">(</span>
        <span class="s2">&quot;Binding time: </span><span class="si">%f</span><span class="s2">s</span><span class="se">\n</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="n">result</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="k">if</span> <span class="n">expand</span> <span class="k">else</span> <span class="s2">&quot;&quot;</span><span class="p">,</span>
        <span class="n">result</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span>
        <span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="n">result</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="k">if</span> <span class="n">expand</span> <span class="k">else</span> <span class="n">result</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span>
    <span class="p">)</span>


<span class="k">for</span> <span class="n">expand</span> <span class="ow">in</span> <span class="p">(</span><span class="kc">True</span><span class="p">,</span> <span class="kc">False</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">((</span><span class="s2">&quot; eager &quot;</span> <span class="k">if</span> <span class="n">expand</span> <span class="k">else</span> <span class="s2">&quot; lazy &quot;</span><span class="p">)</span><span class="o">.</span><span class="n">center</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span> <span class="s2">&quot;-&quot;</span><span class="p">))</span>
    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">RUNS</span><span class="p">):</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot; partial &quot;</span><span class="o">.</span><span class="n">center</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span> <span class="s2">&quot;-&quot;</span><span class="p">))</span>
        <span class="nb">print</span><span class="p">(</span>
            <span class="n">format_result</span><span class="p">(</span>
                <span class="n">bench</span><span class="p">(</span>
                    <span class="p">(</span>
                        <span class="n">partial</span><span class="p">(</span><span class="k">lambda</span> <span class="n">number</span><span class="p">,</span> <span class="n">exp</span><span class="p">:</span> <span class="n">number</span> <span class="o">**</span> <span class="n">exp</span><span class="p">,</span> <span class="n">number</span><span class="p">)</span>
                        <span class="k">for</span> <span class="n">number</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">ENTRIES</span><span class="p">)</span>
                    <span class="p">),</span>
                    <span class="n">expand</span><span class="p">,</span>
                <span class="p">),</span>
                <span class="n">expand</span><span class="p">,</span>
            <span class="p">)</span>
        <span class="p">)</span>

        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot; lambda &quot;</span><span class="o">.</span><span class="n">center</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span> <span class="s2">&quot;-&quot;</span><span class="p">))</span>
        <span class="nb">print</span><span class="p">(</span>
            <span class="n">format_result</span><span class="p">(</span>
                <span class="n">bench</span><span class="p">(</span>
                    <span class="p">(</span>
                        <span class="p">(</span><span class="k">lambda</span> <span class="n">number</span><span class="p">:</span> <span class="k">lambda</span> <span class="n">exp</span><span class="p">:</span> <span class="n">number</span> <span class="o">**</span> <span class="n">exp</span><span class="p">)(</span><span class="n">number</span><span class="p">)</span>
                        <span class="k">for</span> <span class="n">number</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">ENTRIES</span><span class="p">)</span>
                    <span class="p">),</span>
                    <span class="n">expand</span><span class="p">,</span>
                <span class="p">),</span>
                <span class="n">expand</span><span class="p">,</span>
            <span class="p">)</span>
        <span class="p">)</span>
</code></pre></div>



</details>

<h3>Computed sort key</h3>
<div class="highlight"><pre><span></span><code><span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span><span class="w"> </span><span class="nn">math</span><span class="w"> </span><span class="kn">import</span> <span class="n">tan</span>
<span class="gp">&gt;&gt;&gt; </span><span class="nb">sorted</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">),</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">number</span><span class="p">:</span> <span class="n">tan</span><span class="p">(</span><span class="n">number</span><span class="p">))</span>
<span class="go">[11, 18, 15, 12, 19, 16, 13, 10, 17, 14]</span>
</code></pre></div>

<h2>Conclusion</h2>
<ul>
<li>List comprehensions are often faster and always more "Pythonic" than <code>map()</code>, <code>filter()</code>, and <code>reduce()</code>. </li>
<li>Improving upon lambdas while preserving "Pythonicity" is hard.</li>
<li>There are some legitimate use-cases for lambdas... just not that many. </li>
</ul>]]></content:encoded><guid isPermaLink="true">https://philip-trauner.me/blog/post/python-quirks-lambdas</guid></item><item><title>Three days of owning a Dell XPS 15 (9570)</title><link>https://philip-trauner.me/blog/post/three-days-of-owning-a-dell-xps-15</link><description>Detailed Dell XPS 15 (9570) review and postmortem from the perspective of a previous MacBook owner.</description><author>philip.trauner@arztpraxis.io</author><pubDate>Fri, 10 Aug 2018 23:13:10 GMT</pubDate><category>dell</category><category>xps 15</category><category>debian</category><category>linux</category><category>nvidia</category><category>mac</category><content:encoded><![CDATA[<blockquote>
<p>Apple might seriously change up it's MacBook Pro lineup <a href="https://9to5mac.com/2018/04/03/arm-based-mac-opinion/">relatively soon</a> and High Sierra left a sour taste in my mouth, so I don't feel comfortable investing a decent chunk of money into a software and hardware ecosystem that is either going to deteriorate in quality even further or is going to be changing quite drastically in the near future. Either way, I need a new laptop pronto, as my previous Mac is starting to show its age. </p>
</blockquote>
<p>That's the situation lots of people, including me, are currently finding themselves in. My plan to escape this dilemma was simple: Buy a beefy and aesthetically pleasing Windows laptop, install my Linux distro of choice, and hope for the best when it comes to driver support. </p>
<p>After lengthy research (and being influenced by HN comments) I finally decided to go with a <a href="https://www.dell.com/en-us/work/shop/dell-laptops-and-notebooks/new-xps-15/spd/xps-15-9570-laptop/cax15w10p1c1654p">Dell XPS 15 (9570)</a>.</p>
<p>As you'll see, that plan didn't quite pan out the way I had hoped it would.</p>
<h2>Day 1</h2>
<p>The laptop arrived at my home address after about two weeks in transit. Upon opening the cardboard box it came in, I was treated to a stench that no other electronics product I ever purchased reeked of (a mix of cheap plastic and glue). Thankfully that unit itself was not suspect to the same smell.</p>
<h3>Hardware</h3>
<p align="center">
<img src="/static/blog/post/three-days-of-owning-a-dell-xps-15/content/xps-15.png" width="500px" />
</p>
<figcaption>Dell XPS 15 (9570)</figcaption>

<p>The design of the XPS series hasn't really changed much since the introduction of the InfinityEdge display back in 2015, and that's good for once because the understated look that the laptop is going for is definitely nice. It generally looks and feels premium. </p>
<p>My particular unit exhibited noticeable backlight bleed on the bottom left and right corner of the screen. This problem seems to be <a href="https://www.dell.com/community/XPS/9570-Acceptable-backlight-bleed/td-p/6091926">quite common</a>, but Dell does at least offer replacements if <strong>they</strong> deem the backlight bleed unacceptable. Coil whine was also present, but I wasn't able to reproduce it consistently.</p>
<p>One-handed operation of the display hinge is definitely out of the question, as pressing down on the chassis is required to open up the laptop, but that's more of a minor nitpick than actual criticism.</p>
<h3>Initial setup</h3>
<p>Before wiping the Windows partition I performed a "critical" <a href="https://www.dell.com/support/home/us/en/19/product-support/product/xps-15-9570-laptop/drivers">BIOS upgrade</a>.  Dell does upload firmware updates to the <a href="https://fwupd.org/vendorlist">Linux Vendor Firmware Service</a> which can then be installed with <a href="https://github.com/hughsie/fwupd"><code>fwupd</code></a>, but the most recent BIOS version (1.3.0) was not yet available at the time of writing.</p>
<p>With all that out of the way I was finally able to start the installation process of my Linux distribution of choice, which is Debian <code>testing</code> — or so I thought.</p>
<p>The built-in Killer wireless networking adapter (<a href="https://www.qualcomm.com/products/qca6174a-dual-band-wi-fi">Qualcomm Atheros QCA6174</a>) requires a non-free driver to operate, which in turn necessitates a <a href="http://cdimage.debian.org/cdimage/unofficial/non-free/cd-including-firmware/">non-free Debian installer image</a>. Additionally, I had to modify a slew of BIOS settings to successfully boot from a USB drive and subsequently install Debian.</p>
<ul>
<li><code>Secure Boot → Secure Boot Enable → Uncheck</code></li>
</ul>
<p>Only a few Linux distributions support Secure Boot, and Debian isn't one of them yet.</p>
<ul>
<li><code>System Configuration → SATA Configuration → AHCI</code></li>
</ul>
<p>Intel Rapid Storage isn't supported on Linux particularly well (at least out of the box). Configuring SATA for AHCI (Advanced Host Controller Interface) is necessary to make the Debian installer recognize the built-in NVMe drive.</p>
<ul>
<li><code>System Configuration → Thunderbolt Auto Switch → Native Enumeration</code></li>
</ul>
<p>I don't own any Thunderbolt devices, so I was unable to test the difference between  <code>Native Enumeration</code> and <code>BIOS Assist Enumeration</code>, but I disabled <code>Auto Switch</code> because the actual switching takes place every time the laptop powers on and therefor negatively impacts startup-time.</p>
<ul>
<li><code>Intel(R) Software Guard Extensions → Intel(R) SGX Enable → Enabled</code></li>
</ul>
<blockquote>
<p><strong>Intel SGX</strong> is a set of central processing unit (CPU) instruction codes from <strong>Intel</strong> that allows user-level code to allocate private regions of memory, called enclaves, that are protected from processes running at higher privilege levels.</p>
</blockquote>
<p>"Sounds terrible and incredibly vague", I thought. So I disabled it and quickly realized that the built-in Killer wireless networking adapter vanished from <code>lspci</code>'s output. Don't disable it if you want to use WiFi, I guess.</p>
<p>After performing these changes, the Debian installation proceeded without any further interruptions.</p>
<p>After the installation was finished, I was presented with a completely empty boot order list. After lengthy troubleshooting I discovered that boot entries have to be added manually. <strong>Why?</strong></p>
<p align="center">
  <img src="/static/blog/post/three-days-of-owning-a-dell-xps-15/content/boot-order.jpeg" width="400px" alt="Boot entry add dialog" />
</p>
<figcaption>Manually adding GRUB as a boot option</figcaption>

<p>By that point it was already getting late and I decided to continue the next day.</p>
<h2>Day 2</h2>
<p>With Debian installed it was time to run my <a href="https://github.com/PhilipTrauner/dotfiles">trusty setup scripts</a> to make the machine feel like home, but issues arose pretty much immediately.</p>
<p>Installing any desktop environment / window manager (in my case <code>i3</code>) pulls in <code>xserver-xorg-video-nouveau</code> on Debian. After the first reboot, nothing seemed out of the ordinary, until my system froze up after about a minute of very light usage (sitting idle on the <code>lightdm</code> login screen).</p>
<p>Turns out that <code>nouveau</code> currently doesn't play well with the XPS 15's GTX 1050 Ti and also lacks the courtesy to crash early and predictably. To temporarily get the system up and running again, a simple <code>nomodeset</code> is not sufficient. Instead, <code>nouveau.modeset=0</code> has to be appended to the <a href="https://wiki.archlinux.org/index.php/kernel_parameters#GRUB">kernel arguments</a>. </p>
<p>But that's just a temporary fix. Thankfully, there are still two other more longterm solutions remaining: </p>
<ol>
<li>Install the official NVIDIA drivers (<em>ugh</em>) and utilize <code>bumblebee</code> to switch between integrated and dedicated graphics.</li>
<li>Disable the dedicated graphics card outright by removing <code>xserver-xorg-video-nouveau</code>.</li>
</ol>
<p>I went with the first option. My workflow isn't really enhanced by a dedicated graphics card, but because I involuntarily purchased the 1050 Ti I didn't want the money to go to waste, so I figured that I should at least setup <code>bumblebee</code> to be able to switch between both graphics solutions.</p>
<p>There's just one problem minor problem: <code>bbswitch</code>, which is what <code>bumblebee</code> uses to power down the graphics card when it is not in use, does not work properly on Linux 4.17. It claims that it does (<code>/proc/acpi/bbswitch</code> → <code>0000:01:00.0 OFF</code>), but digging around in sysfs (<code>/sys/class/pci_bus/0000:01/power/runtime_enabled</code> → <code>enabled</code>) and occasional fan spin-ups that are not tied to CPU usage tell us a different story. It also fails to properly power on the card when prompted to by <code>optirun</code> or <code>primusrun</code> (Although there appears to be a <a href="https://github.com/JackHack96/dell-xps-9570-ubuntu-respin">partial fix</a> that forgoes <code>bbswitch</code> usage on startup). </p>
<p>Surprisingly, upgrading to Linux 4.18-rc4 fixed the graphics card shutdown on boot, but now <code>bbswitch</code> re-enables the graphics card every time the computer leaves lid-sleep, upon which the card can not be shut down anymore. This effectively renders <code>bumblebee</code> useless and absolutely destroys battery life. <code>i3-bar</code> suggests a drop from 11 hours to just three.</p>
<div class="highlight"><pre><span></span><code>bbswitch: loading out-of-tree module taints kernel.
bbswitch: version 0.8
bbswitch: Found integrated VGA device 0000:00:02.0: \_SB_.PCI0.GFX0
bbswitch: Found discrete VGA device 0000:01:00.0: \_SB_.PCI0.PEG0.PEGP
bbswitch: detected an Optimus _DSM function
bbswitch: disabling discrete graphic
</code></pre></div>

<figcaption>dmesg output of bbswitch</figcaption>

<p>As the troubleshooting commenced I was feeling less and less satisfied with my purchase decision. Thankfully, it was already getting late, which I used as an excuse to go to bed.</p>
<h2>Day 3</h2>
<p>After some graphics card related nightmares I still had some fight left in me and continued on.</p>
<p>I wanted to tackle the lack of tap-to-click first, as it had already gotten on my nerves the day before. Debian utilizes <code>libinput</code> to handle input devices like touchpads. It performs admirably overall, although it is certainly lacking in the configuration department when compared to <a href="https://github.com/p2rkw/xf86-input-mtrack"><code>mtrack</code></a> and <a href="https://packages.debian.org/en/buster/xserver-xorg-input-synaptics"><code>synaptics</code></a>. <a href="https://wiki.archlinux.org/index.php/Libinput">Tap-to-click</a> can be enabled though, and from my experience it works significantly better than the two alternatives, as once enabled there is basically no pointer jitter. With all of that being said, I was still thoroughly disappointed as I was used to the excellent trackpad handling on macOS. It seems that <a href="https://news.ycombinator.com/item?id=17547817">I'm not the only one feeling this way</a>, but nobody has taken the initiative to do something about it yet.</p>
<p>Anyway, next I decided to look into ways to improve the abysmal battery life.<br>
The ever helpful <a href="https://wiki.archlinux.org/index.php/Laptop">Arch Linux Wiki</a> suggests a tool called <a href="https://linrunner.de/en/tlp/tlp.html"><code>tlp</code></a> specifically for ThinkPads, but nowadays it basically work on any laptop.</p>
<p>"Sounds great!", I thought, until I noticed that I was unable to establish WiFi connections after installing it. Even after a <code>apt-get purge tlp</code> the Killer wireless networking adapter stubbornly refused to participate in any key-exchanges. After rigorous troubleshooting I realized that <code>tlp</code> had pulled in <code>network-manager</code> without me noticing, which perfectly explains basically every kind of networking anomaly, as <code>network-manager</code> doesn't play well with <code>systemd-networkd</code>.</p>
<p>Another potential issue I avoided thanks to the Arch Linux Wiki was a conflict between <code>bbswitch</code> and the PCI power management functionality of <code>tlp</code>. The PCI address of the NVIDIA graphics card has to be excluded from <code>tlp</code>'s runtime power management, otherwise bad things might happen. </p>
<div class="highlight"><pre><span></span><code># Exclude PCI(e) device adresses the following list from Runtime PM
# (separate with spaces). Use lspci to get the adresses (1st column).
RUNTIME_PM_BLACKLIST=&quot;01:00.0&quot;
</code></pre></div>

<figcaption>Appending the PCI address of the NVIDIA graphics card to the tlp runtime power management blacklist prevents a potential conflict with bbswitch</figcaption>

<p>All things considering, I probably wouldn't have run into the issue in the first place as <code>bbswitch</code> still wasn't working properly anyway.</p>
<p>After another round of extensive troubleshooting with the goal of permanently disabling the graphics card I decided to give up. I scrubbed the drive, and contacted Dell support to arrange a refund. The support agent was very accommodating and I was surprised to find out that Dell even offers to send someone to retrieve the laptop for free. </p>
<h2>Conclusion</h2>
<p>If you can handle pretty terrible battery life, don't mind installing the closed-source NVIDIA graphics driver, want to carry around a mouse, can ignore the coil-whine and the backlight-bleed, and are willing to put in a few days to get everything up and running, then you probably won't regret your purchase. Otherwise the only solution I can offer you is to bite the bullet and buy another overpriced MacBook that thermal-throttles even worse than the XPS 15. Its what I did, and its also probably what you'll do too.</p>]]></content:encoded><guid isPermaLink="true">https://philip-trauner.me/blog/post/three-days-of-owning-a-dell-xps-15</guid></item><item><title>Python Quirks: Comments</title><link>https://philip-trauner.me/blog/post/python-quirks-comments</link><description>Performance comparison of various Python comment types.</description><author>philip.trauner@arztpraxis.io</author><pubDate>Wed, 12 Jul 2017 16:28:48 GMT</pubDate><category>python</category><category>quirk</category><category>comments</category><category>ast</category><category>speed</category><category>docstrings</category><category>strings</category><content:encoded><![CDATA[<div class="highlight"><pre><span></span><code><span class="c1"># I&#39;m a comment.</span>

<span class="sd">&quot;&quot;&quot;</span>
<span class="sd">I&#39;m also a comment.</span>
<span class="sd">&quot;&quot;&quot;</span>

<span class="s2">&quot;I&#39;m a comment too!&quot;</span>

<span class="mi">87_104_121_32_97_109_32_73_32_97_32_99_111_109_109_101_110_116_63</span>

<span class="p">[</span><span class="s2">&quot;I&#39;m&quot;</span><span class="p">,</span> <span class="s2">&quot;also&quot;</span><span class="p">,</span> <span class="s2">&quot;a&quot;</span><span class="p">,</span> <span class="s2">&quot;comment&quot;</span><span class="p">,</span> <span class="p">(</span><span class="s2">&quot;surprisingly.&quot;</span><span class="p">,)]</span>
</code></pre></div>

<figcaption>Totally valid Python program.</figcaption>

<p>Only one of these “comments” is a real one. <br>
Well, technically speaking all of them are comments, the real difference between them is speed and functionality.</p>
<p>Let's generate some code to find out what the actual differences between them are.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">time</span><span class="w"> </span><span class="kn">import</span> <span class="n">time</span>

<span class="c1"># style comments</span>
<span class="n">real_comments_code</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span>
<span class="s2">&quot;string literal style comments&quot;</span>
<span class="n">fake_comments_code</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span>

<span class="k">for</span> <span class="n">comment_id</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">500000</span><span class="p">):</span>
    <span class="n">comment</span> <span class="o">=</span> <span class="s2">&quot;I&#39;m comment number </span><span class="si">%i</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="n">comment_id</span>

    <span class="n">real_comments_code</span> <span class="o">+=</span> <span class="s2">&quot;# </span><span class="si">%s</span><span class="se">\n</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="n">comment</span>
    <span class="n">fake_comments_code</span> <span class="o">+=</span> <span class="s1">&#39;&quot;</span><span class="si">%s</span><span class="s1">&quot;</span><span class="se">\n</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="n">comment</span>

<span class="n">start_time</span> <span class="o">=</span> <span class="n">time</span><span class="p">()</span>
<span class="nb">compile</span><span class="p">(</span><span class="n">real_comments_code</span><span class="p">,</span> <span class="s2">&quot;&lt;string&gt;&quot;</span><span class="p">,</span> <span class="s2">&quot;exec&quot;</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;Real comments took: </span><span class="si">%s</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start_time</span><span class="p">))</span>

<span class="n">start_time</span> <span class="o">=</span> <span class="n">time</span><span class="p">()</span>
<span class="nb">compile</span><span class="p">(</span><span class="n">fake_comments_code</span><span class="p">,</span> <span class="s2">&quot;&lt;string&gt;&quot;</span><span class="p">,</span> <span class="s2">&quot;exec&quot;</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;Fake comments took: </span><span class="si">%s</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start_time</span><span class="p">))</span>
</code></pre></div>

<figcaption>Executing speed comparison (real comments vs. fake comments).</figcaption>

<div class="highlight"><pre><span></span><code>Real comments took: 0.08859992027282715
Fake comments took: 1.4969751834869385
</code></pre></div>

<p>The “#” comment variant is clearly favorable over the isolated string literal one in this completely unrealistic scenario, so if you ever wanted to include 500000 comments in your code, they are clearly the way to go.</p>
<p>But what's actually happening here, and because this is Python, can we somehow abuse this behavior? Let's examine the AST of our two automatically generated programs to find out.</p>
<div class="highlight"><pre><span></span><code>Module
</code></pre></div>

<figcaption>Syntax tree for real comments</figcaption>

<div class="highlight"><pre><span></span><code>Module
  Expr
    Str
  Expr
    Str
  Expr
    Str
  Expr
    Str
  Expr
    Str
  ...
</code></pre></div>

<figcaption>Syntax tree for fake comments</figcaption>

<p>Real comments are simply left out of the final Python AST, just as one would expect. That's exactly where the speed difference is taking place, string literals still need to be evaluated on program runtime and you should never abuse them for comments. But that doesn't mean that they are useless.</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">documented_function</span><span class="p">():</span>
    <span class="c1"># I&#39;m __doc__</span>
    <span class="s2">&quot;No, I&#39;m __doc__!&quot;</span>
<span class="w">    </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd">    Shut up you two, I&#39;m the real __doc__ here! (well, maybe not)</span>
<span class="sd">    &quot;&quot;&quot;</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;I&#39;m a documented function.&quot;</span><span class="p">)</span>


<span class="nb">print</span><span class="p">(</span><span class="n">documented_function</span><span class="o">.</span><span class="vm">__doc__</span><span class="p">)</span>
</code></pre></div>

<figcaption>"No, I'm __doc__!" (""" style docstrings are the preferred in the spec)</figcaption>

<p>Citing <a href="https://www.python.org/dev/peps/pep-0257/">PEP-0257</a>:</p>
<blockquote>
<p>A docstring is a string literal that occurs as the first statement in a module, function, class, or method definition. Such a docstring becomes the <strong>doc</strong> special attribute of that object.</p>
</blockquote>
<p>Python not only supports isolated strings literals, it essentially supports isolated anything. What's interesting here is, that all statements are evaluated. In combination with properties this behavior allows for some very “Ruby-esque” code.</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">ClassWithProperty</span><span class="p">:</span>
    <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">lazy</span><span class="o">=</span><span class="kc">True</span><span class="p">):</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">_instance_property</span> <span class="o">=</span> <span class="kc">None</span>

        <span class="k">if</span> <span class="ow">not</span> <span class="n">lazy</span><span class="p">:</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">instance_property</span>

    <span class="nd">@property</span>
    <span class="k">def</span><span class="w"> </span><span class="nf">instance_property</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_instance_property</span> <span class="o">==</span> <span class="kc">None</span><span class="p">:</span>
            <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;Working hard, but I&#39;m still lazy.&quot;</span><span class="p">)</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">_instance_property</span> <span class="o">=</span> <span class="s2">&quot;I was born lazy.&quot;</span>
        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_instance_property</span>


<span class="n">ClassWithProperty</span><span class="p">(</span><span class="n">lazy</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
<span class="n">ClassWithProperty</span><span class="p">()</span>
</code></pre></div>

<figcaption>"Ruby-esque" lazy loading</figcaption>

<p>In conclusion: <strong>Don’t abuse the language</strong> (well, a little can’t hurt, right?)</p>]]></content:encoded><guid isPermaLink="true">https://philip-trauner.me/blog/post/python-quirks-comments</guid></item></channel></rss>