<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Gangmax Blog</title>
  
  <subtitle>自由之思想，独立之精神</subtitle>
  <link href="https://gangmax.me/atom.xml" rel="self"/>
  
  <link href="https://gangmax.me/"/>
  <updated>2026-04-17T07:25:55.196Z</updated>
  <id>https://gangmax.me/</id>
  
  <author>
    <name>Max Huang</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Source Map in JavaScript</title>
    <link href="https://gangmax.me/blog/2026/04/01/source-map-in-javascript/"/>
    <id>https://gangmax.me/blog/2026/04/01/source-map-in-javascript/</id>
    <published>2026-04-01T18:36:13.000Z</published>
    <updated>2026-04-17T07:25:55.196Z</updated>
    
    <content type="html"><![CDATA[<p>After I knew “<a href="https://www.reddit.com/r/ClaudeAI/comments/1s8ifm6/claude_code_source_code_has_been_leaked_via_a_map/">Claude code source code has been leaked via a map file in their npm registry</a>“, I took some time with ChatGPT to get more details about the “source map” concept in the JavaScript ecosystem.</p><hr><h4 id="你说："><a href="#你说：" class="headerlink" title="你说："></a>你说：</h4><p>在一个TypeScript编写的project中，“map”文件的用途是什么？</p><h4 id="ChatGPT-说："><a href="#ChatGPT-说：" class="headerlink" title="ChatGPT 说："></a>ChatGPT 说：</h4><p>在一个用 TypeScript 编写的项目中，<code>.map</code> 文件通常指的是 <strong>source map（源码映射文件）</strong>，它的作用可以一句话概括：<strong>把编译后的 JavaScript 映射回原始的 TypeScript 代码</strong>。<br>下面把它讲清楚一点——不绕弯：</p><hr><h2 id="一、为什么会有-map-文件？"><a href="#一、为什么会有-map-文件？" class="headerlink" title="一、为什么会有 .map 文件？"></a>一、为什么会有 <code>.map</code> 文件？</h2><p>TypeScript 代码最终会被编译成 JavaScript，比如：<br>TypeScript<br>&#x2F;&#x2F; 原始 TS<br>let x: number = 10;<br>编译后：<br>JavaScript<br>var x = 10;<br>问题来了：<br>👉 浏览器、Node.js 实际运行的是 JS，不是 TS<br>👉 报错、调试、堆栈信息也都是 JS 行号<br>这时候就会很难定位问题。</p><hr><h2 id="二、-map-文件在干嘛？"><a href="#二、-map-文件在干嘛？" class="headerlink" title="二、.map 文件在干嘛？"></a>二、<code>.map</code> 文件在干嘛？</h2><p><code>.map</code> 文件记录了一种“对照关系”：<br>👉 JS 的第几行，对应 TS 的第几行<br>简单理解就是一个“翻译字典”：<br>app.js  第10行  →  app.ts 第3行</p><hr><h2 id="三、实际效果（很关键）"><a href="#三、实际效果（很关键）" class="headerlink" title="三、实际效果（很关键）"></a>三、实际效果（很关键）</h2><h3 id="1️⃣-调试体验"><a href="#1️⃣-调试体验" class="headerlink" title="1️⃣ 调试体验"></a>1️⃣ 调试体验</h3><p>在浏览器 DevTools 或 VS Code 里：</p><ul><li>你看到的是 <code>.ts</code> 文件（不是 <code>.js</code>）</li><li>可以直接在 TS 里打断点</li><li>报错会指向 TS 行号<br>否则你只能面对编译后的 JS（尤其压缩后更难看）</li></ul><hr><h3 id="2️⃣-错误堆栈更可读"><a href="#2️⃣-错误堆栈更可读" class="headerlink" title="2️⃣ 错误堆栈更可读"></a>2️⃣ 错误堆栈更可读</h3><p>没有 map：<br>Error at app.js:482<br>有 map：<br>Error at app.ts:23<br>👉 这差别基本就是“能不能定位问题”的差别</p><hr><h2 id="四、-map-文件长什么样？"><a href="#四、-map-文件长什么样？" class="headerlink" title="四、.map 文件长什么样？"></a>四、<code>.map</code> 文件长什么样？</h2><p>它本质是一个 JSON，例如：<br>JSON<br>{<br>“version”: 3,<br>“file”: “app.js”,<br>“sources”: [“app.ts”],<br>“mappings”: “AAAA…”<br>}<br>你不用读懂 <code>mappings</code>，那是压缩编码的映射规则。</p><hr><h2 id="五、怎么生成？"><a href="#五、怎么生成？" class="headerlink" title="五、怎么生成？"></a>五、怎么生成？</h2><p>在 <code>tsconfig.json</code> 里开启：<br>JSON<br>{<br>“compilerOptions”: {<br>“sourceMap”: true<br>}<br>}<br>编译后会得到：<br>app.ts<br>app.js<br>app.js.map</p><hr><h2 id="六、要不要在生产环境保留？"><a href="#六、要不要在生产环境保留？" class="headerlink" title="六、要不要在生产环境保留？"></a>六、要不要在生产环境保留？</h2><p>这是个很现实的问题：</p><h3 id="✅-保留的好处"><a href="#✅-保留的好处" class="headerlink" title="✅ 保留的好处"></a>✅ 保留的好处</h3><ul><li>方便线上错误定位（比如配合日志系统）</li></ul><h3 id="❌-风险"><a href="#❌-风险" class="headerlink" title="❌ 风险"></a>❌ 风险</h3><ul><li>暴露源码（别人可以反推你的 TS 代码）<br>👉 常见做法：</li><li>内部系统：保留 ✔</li><li>公网项目：通常不公开 <code>.map</code> ❌（但可私有上传到监控工具）</li></ul><hr><h2 id="七、一句话总结"><a href="#七、一句话总结" class="headerlink" title="七、一句话总结"></a>七、一句话总结</h2><p><code>.map</code> 文件就是：</p><blockquote><p><strong>让“运行时代码（JS）”和“开发时代码（TS）”之间建立映射，从而实现可读调试和准确报错定位。</strong></p></blockquote><hr><p>如果你愿意，我可以帮你看一个真实 <code>.map</code> 文件是怎么对应代码的，或者讲讲它和 Webpack &#x2F; Vite 里的 <code>devtool</code> 有什么关系。</p><h4 id="你说：-1"><a href="#你说：-1" class="headerlink" title="你说："></a>你说：</h4><p>“.map”文件中是否会保留ts源代码的原始变量名，函数名等信息？</p><h4 id="ChatGPT-说：-1"><a href="#ChatGPT-说：-1" class="headerlink" title="ChatGPT 说："></a>ChatGPT 说：</h4><p>短答案：<strong>通常会保留，而且往往是“完整保留”——但取决于配置和构建方式。</strong></p><hr><h2 id="一、默认情况下（TypeScript-编译）"><a href="#一、默认情况下（TypeScript-编译）" class="headerlink" title="一、默认情况下（TypeScript 编译）"></a>一、默认情况下（TypeScript 编译）</h2><p>当你在 <code>tsconfig.json</code> 里开启：<br>JSON<br>{<br>“compilerOptions”: {<br>“sourceMap”: true<br>}<br>}<br>生成的 <code>.map</code> 文件里：</p><h3 id="✅-会包含的信息"><a href="#✅-会包含的信息" class="headerlink" title="✅ 会包含的信息"></a>✅ 会包含的信息</h3><ul><li>原始 <code>.ts</code> 文件路径</li><li>行号、列号映射</li><li><strong>变量名、函数名（基本都会保留）</strong><br>👉 因为 TypeScript 编译本身<strong>不会混淆（minify）代码</strong><br>举个简单例子：<br>TypeScript<br>function calculateTotal(price: number) {<br>return price * 1.2;<br>}<br>编译后 JS 可能还是：<br>JavaScript<br>function calculateTotal(price) {<br>return price * 1.2;<br>}<br><code>.map</code> 里自然也会记录 <code>calculateTotal</code> 和 <code>price</code></li></ul><hr><h2 id="二、关键点：-map-能不能“反推出源码”？"><a href="#二、关键点：-map-能不能“反推出源码”？" class="headerlink" title="二、关键点：.map 能不能“反推出源码”？"></a>二、关键点：<code>.map</code> 能不能“反推出源码”？</h2><p>这取决于一个非常重要的字段：</p><h3 id="🔑-sourcesContent"><a href="#🔑-sourcesContent" class="headerlink" title="🔑 sourcesContent"></a>🔑 <code>sourcesContent</code></h3><p><code>.map</code> 文件可能长这样：<br>JSON<br>{<br>“sources”: [“app.ts”],<br>“sourcesContent”: [<br>“function calculateTotal(price: number) { … }”<br>]<br>}</p><h3 id="两种情况："><a href="#两种情况：" class="headerlink" title="两种情况："></a>两种情况：</h3><h4 id="✅-有-sourcesContent"><a href="#✅-有-sourcesContent" class="headerlink" title="✅ 有 sourcesContent"></a>✅ 有 <code>sourcesContent</code></h4><p>👉 <strong>直接包含完整 TS 源码</strong></p><ul><li>可以几乎完整还原你的代码</li><li>连注释都可能在</li></ul><h4 id="❌-没有-sourcesContent"><a href="#❌-没有-sourcesContent" class="headerlink" title="❌ 没有 sourcesContent"></a>❌ 没有 <code>sourcesContent</code></h4><p>👉 只有路径 + 映射关系</p><ul><li>需要你本地有 <code>.ts</code> 文件才能还原</li><li>外部人较难直接看到源码</li></ul><hr><h2 id="三、如果用了打包工具（Webpack-Vite-esbuild）"><a href="#三、如果用了打包工具（Webpack-Vite-esbuild）" class="headerlink" title="三、如果用了打包工具（Webpack &#x2F; Vite &#x2F; esbuild）"></a>三、如果用了打包工具（Webpack &#x2F; Vite &#x2F; esbuild）</h2><p>事情就没那么“单纯”了：</p><h3 id="1️⃣-如果没有压缩（development）"><a href="#1️⃣-如果没有压缩（development）" class="headerlink" title="1️⃣ 如果没有压缩（development）"></a>1️⃣ 如果没有压缩（development）</h3><ul><li>变量名 ✔ 保留</li><li>函数名 ✔ 保留</li><li>调试体验很好</li></ul><hr><h3 id="2️⃣-如果开启压缩（production）"><a href="#2️⃣-如果开启压缩（production）" class="headerlink" title="2️⃣ 如果开启压缩（production）"></a>2️⃣ 如果开启压缩（production）</h3><p>比如用了：</p><ul><li>Terser</li><li>esbuild minify<br>代码可能变成：<br>JavaScript<br>function a(b){return b*1.2}<br>这时候：<br>项目<br>是否保留<br>JS 中变量名<br>❌ 被压缩<br><code>.map</code> 映射回 TS 名<br>✅ 仍然可以<br>源码恢复能力<br>⚠️ 取决于 sourcesContent<br>👉 重点来了：<blockquote><p>即使 JS 被压缩，<strong><code>.map</code> 仍然可以映射回原始变量名</strong></p></blockquote></li></ul><hr><h2 id="四、实际风险（你真正想问的点）"><a href="#四、实际风险（你真正想问的点）" class="headerlink" title="四、实际风险（你真正想问的点）"></a>四、实际风险（你真正想问的点）</h2><p>你其实是在问：</p><blockquote><p><code>.map</code> 会不会泄露源码细节？<br>答案是：<strong>有可能，而且很常见。</strong></p></blockquote><h3 id="风险等级："><a href="#风险等级：" class="headerlink" title="风险等级："></a>风险等级：</h3><p>情况<br>风险<br>有 <code>sourcesContent</code><br>🔥🔥🔥 完整源码泄露<br>无 <code>sourcesContent</code><br>⚠️ 可读性较高（仍能推断逻辑）<br>+ 未混淆代码<br>🔥🔥🔥 非常容易还原<br>+ 已混淆<br>⚠️ 但仍可调试</p><hr><h2 id="五、行业常见做法"><a href="#五、行业常见做法" class="headerlink" title="五、行业常见做法"></a>五、行业常见做法</h2><h3 id="🟢-开发环境"><a href="#🟢-开发环境" class="headerlink" title="🟢 开发环境"></a>🟢 开发环境</h3><ul><li>开启 source map ✔</li><li>保留所有信息 ✔</li></ul><h3 id="🟡-生产环境（推荐）"><a href="#🟡-生产环境（推荐）" class="headerlink" title="🟡 生产环境（推荐）"></a>🟡 生产环境（推荐）</h3><ul><li>生成 <code>.map</code> ✔</li><li><strong>不对外公开</strong> ❗</li><li>上传到错误监控系统（如 Sentry）</li></ul><h3 id="🔴-高安全项目"><a href="#🔴-高安全项目" class="headerlink" title="🔴 高安全项目"></a>🔴 高安全项目</h3><ul><li>关闭 source map ❌</li><li>或去掉 <code>sourcesContent</code></li></ul><hr><h2 id="六、一句话总结"><a href="#六、一句话总结" class="headerlink" title="六、一句话总结"></a>六、一句话总结</h2><blockquote><p><code>.map</code> 文件不仅会保留变量名和函数名，在某些配置下甚至包含完整 TypeScript 源码；是否“泄露”，完全取决于你怎么生成和发布它。</p></blockquote><hr><p>如果你愿意，我可以帮你分析你项目的构建配置（比如 Vite &#x2F; Webpack），看看现在到底有没有“无意中在公网裸奔源码”。</p><h4 id="你说：-2"><a href="#你说：-2" class="headerlink" title="你说："></a>你说：</h4><p>也就是说：使用TypeScript编程，如果要保证调试的方便性，“.map”文件是必须的，对吗？</p><h4 id="ChatGPT-说：-2"><a href="#ChatGPT-说：-2" class="headerlink" title="ChatGPT 说："></a>ChatGPT 说：</h4><p>可以这么说，但我帮你把话说得更“工程化一点”：</p><blockquote><p><strong><code>.map</code> 文件不是“必须”，但几乎是“不可替代”的调试工具。</strong></p></blockquote><hr><h2 id="一、从结果看：没有-map-会怎样？"><a href="#一、从结果看：没有-map-会怎样？" class="headerlink" title="一、从结果看：没有 .map 会怎样？"></a>一、从结果看：没有 <code>.map</code> 会怎样？</h2><p>如果你不用 source map：</p><ul><li>浏览器里看到的是编译后的 JS</li><li>报错定位在 <code>.js</code> 文件</li><li>行号、变量名可能已经变化</li><li>打断点基本只能打在 JS 上<br>👉 现实体验就是：<br><strong>能调，但很痛苦，尤其是项目一大就会崩溃</strong></li></ul><hr><h2 id="二、有-map-时的变化（质变）"><a href="#二、有-map-时的变化（质变）" class="headerlink" title="二、有 .map 时的变化（质变）"></a>二、有 <code>.map</code> 时的变化（质变）</h2><p>有了 <code>.map</code>：</p><ul><li>DevTools &#x2F; VS Code 直接显示 <code>.ts</code></li><li>断点打在 TS 上 ✔</li><li>报错指向 TS 行号 ✔</li><li>可以逐行调试 TS ✔<br>👉 本质上就是：<blockquote><p><strong>你“假装”在直接运行 TypeScript</strong></p></blockquote></li></ul><hr><h2 id="三、所以它是不是“必须”？分情况说"><a href="#三、所以它是不是“必须”？分情况说" class="headerlink" title="三、所以它是不是“必须”？分情况说"></a>三、所以它是不是“必须”？分情况说</h2><h3 id="🟢-开发环境（Dev）"><a href="#🟢-开发环境（Dev）" class="headerlink" title="🟢 开发环境（Dev）"></a>🟢 开发环境（Dev）</h3><p>👉 可以很明确地说：</p><blockquote><p><strong>基本等于必须</strong><br>否则调试效率会明显下降，不是“慢一点”，而是“难很多”。</p></blockquote><hr><h3 id="🟡-生产环境（Prod）"><a href="#🟡-生产环境（Prod）" class="headerlink" title="🟡 生产环境（Prod）"></a>🟡 生产环境（Prod）</h3><p>👉 就完全不同了：</p><ul><li>不是为了调试（用户不会帮你 debug）</li><li>主要用于错误追踪（比如 Sentry）<br>所以：<blockquote><p><strong>可以有，但通常不对外暴露</strong></p></blockquote></li></ul><hr><h2 id="四、一个更准确的结论"><a href="#四、一个更准确的结论" class="headerlink" title="四、一个更准确的结论"></a>四、一个更准确的结论</h2><p>你这句话可以稍微修正为：</p><blockquote><p><strong>在 TypeScript 项目中，为了获得良好的调试体验，source map 几乎是必不可少的；但它并不是运行代码所必需的。</strong></p></blockquote><hr><h2 id="五、顺带给你一个实用建议"><a href="#五、顺带给你一个实用建议" class="headerlink" title="五、顺带给你一个实用建议"></a>五、顺带给你一个实用建议</h2><p>很多人踩的坑是：<br>JSON<br>{<br>“sourceMap”: true<br>}<br>但：<br>👉 忘了在打包工具里开启（比如 Vite &#x2F; Webpack）<br>结果就是：</p><ul><li>TS 编译有 map ✔</li><li>打包后 map 丢了 ❌</li></ul><hr><h2 id="六、总结一句人话版"><a href="#六、总结一句人话版" class="headerlink" title="六、总结一句人话版"></a>六、总结一句人话版</h2><blockquote><p>不开 <code>.map</code> 也能跑，但你会调试到怀疑人生；开了 <code>.map</code>，世界恢复正常。</p></blockquote><hr><p>如果你想更深入一点，我可以帮你拆一下：</p><ul><li>Chrome DevTools 是怎么“用” <code>.map</code> 的</li><li>或者 Vite &#x2F; Webpack 里不同 <code>source map</code> 模式的区别（这个在性能和安全上差别挺大）</li></ul><h4 id="你说：-3"><a href="#你说：-3" class="headerlink" title="你说："></a>你说：</h4><p>所以即使在生产环境中，为了使得出现错误后便于定位问题，依然需要保留”.map”文件，对吗？</p><h4 id="ChatGPT-说：-3"><a href="#ChatGPT-说：-3" class="headerlink" title="ChatGPT 说："></a>ChatGPT 说：</h4><p>可以这么理解，但需要加一个关键前提，否则就容易踩坑：</p><blockquote><p><strong>生产环境“需要生成 <code>.map</code> 文件”，但通常“不应该对外公开”。</strong></p></blockquote><hr><h2 id="一、为什么生产环境仍然需要-map？"><a href="#一、为什么生产环境仍然需要-map？" class="headerlink" title="一、为什么生产环境仍然需要 .map？"></a>一、为什么生产环境仍然需要 <code>.map</code>？</h2><p>因为线上错误不可避免，而你需要<strong>还原真实源码位置</strong>。<br>例如用户环境报错：<br>Error at app.min.js:1:84231<br>👉 这基本等于没信息（压缩后的一行代码）<br>但如果你有 <code>.map</code>：<br>Error at src&#x2F;services&#x2F;userService.ts:57<br>👉 立刻能定位问题</p><hr><h2 id="二、关键区别：生成-vs-暴露"><a href="#二、关键区别：生成-vs-暴露" class="headerlink" title="二、关键区别：生成 vs 暴露"></a>二、关键区别：生成 vs 暴露</h2><p>这是很多人容易混淆的点：</p><h3 id="✅-应该做的"><a href="#✅-应该做的" class="headerlink" title="✅ 应该做的"></a>✅ 应该做的</h3><ul><li>构建时生成 <code>.map</code></li><li>上传到错误监控平台（如 Sentry）</li><li>内部保存</li></ul><h3 id="❌-不建议做的"><a href="#❌-不建议做的" class="headerlink" title="❌ 不建议做的"></a>❌ 不建议做的</h3><ul><li>把 <code>.map</code> 文件部署到公网服务器</li><li>让用户可以直接访问 <code>app.js.map</code></li></ul><hr><h2 id="三、为什么不能随便公开-map？"><a href="#三、为什么不能随便公开-map？" class="headerlink" title="三、为什么不能随便公开 .map？"></a>三、为什么不能随便公开 <code>.map</code>？</h2><p>因为它可能包含：</p><ul><li>原始 TypeScript 代码（<code>sourcesContent</code>）</li><li>真实变量名 &#x2F; 业务逻辑</li><li>注释（甚至敏感信息）<br>👉 本质上等于：<blockquote><p><strong>把源码附赠给所有用户</strong></p></blockquote></li></ul><hr><h2 id="四、行业标准做法（很重要）"><a href="#四、行业标准做法（很重要）" class="headerlink" title="四、行业标准做法（很重要）"></a>四、行业标准做法（很重要）</h2><h3 id="典型流程："><a href="#典型流程：" class="headerlink" title="典型流程："></a>典型流程：</h3><p>1️⃣ 构建：</p><ul><li>生成 <code>.js</code></li><li>生成 <code>.js.map</code><br>2️⃣ 发布：</li><li>只上传 <code>.js</code></li><li><strong>不上传 <code>.map</code></strong><br>3️⃣ 错误监控：</li><li>把 <code>.map</code> 上传到监控系统</li><li>用来解析堆栈</li></ul><hr><h2 id="五、不同工具的常见配置思路"><a href="#五、不同工具的常见配置思路" class="headerlink" title="五、不同工具的常见配置思路"></a>五、不同工具的常见配置思路</h2><h3 id="比如-Vite"><a href="#比如-Vite" class="headerlink" title="比如 Vite"></a>比如 Vite</h3><p>TypeScript<br>build: {<br>sourcemap: true<br>}<br>👉 但部署时不带 <code>.map</code></p><hr><h3 id="比如-Webpack"><a href="#比如-Webpack" class="headerlink" title="比如 Webpack"></a>比如 Webpack</h3><p>JavaScript<br>devtool: ‘source-map’<br>👉 配合隐藏 map：<br>JavaScript<br>devtool: ‘hidden-source-map’<br>（生成但不暴露）</p><hr><h2 id="六、一句话结论"><a href="#六、一句话结论" class="headerlink" title="六、一句话结论"></a>六、一句话结论</h2><blockquote><p>生产环境中，<code>.map</code> 文件是“排查问题的利器”，但前提是“只给自己用，不给别人看”。</p></blockquote><hr><h2 id="七、给你一个实用判断标准"><a href="#七、给你一个实用判断标准" class="headerlink" title="七、给你一个实用判断标准"></a>七、给你一个实用判断标准</h2><p>你可以用这个简单原则：</p><ul><li>❓ 你需要排查线上错误吗？<br>→ ✔ 保留 <code>.map</code></li><li>❓ 你希望用户看到你的源码吗？<br>→ ❌ 不要公开 <code>.map</code></li></ul><hr><p>如果你愿意，我可以帮你检查你当前项目（比如 Vite &#x2F; Webpack）的配置，看看现在是“安全状态”还是已经在无意中把源码暴露出去了。</p><h4 id="你说：-4"><a href="#你说：-4" class="headerlink" title="你说："></a>你说：</h4><p>这个问题是TypeScript语言独有的吗？Javascript有类似的问题吗？</p><h4 id="ChatGPT-说：-4"><a href="#ChatGPT-说：-4" class="headerlink" title="ChatGPT 说："></a>ChatGPT 说：</h4><p>不是 TypeScript 独有的问题，<strong>JavaScript 同样有这个问题，而且在很多场景下更“明显”</strong>。</p><hr><h2 id="一、先说结论"><a href="#一、先说结论" class="headerlink" title="一、先说结论"></a>一、先说结论</h2><blockquote><p><strong>Source Map 是“编译&#x2F;构建体系”的机制，不是 TypeScript 独有的语言特性。</strong><br>只要你的代码经过“转换、压缩、打包”，就会涉及同样的问题。</p></blockquote><hr><h2 id="二、JavaScript-什么时候也需要-map？"><a href="#二、JavaScript-什么时候也需要-map？" class="headerlink" title="二、JavaScript 什么时候也需要 .map？"></a>二、JavaScript 什么时候也需要 <code>.map</code>？</h2><p>在现代前端开发中，JS 早就不是“原生直接运行”的那种简单形态了：</p><h3 id="1️⃣-代码压缩（Minify）"><a href="#1️⃣-代码压缩（Minify）" class="headerlink" title="1️⃣ 代码压缩（Minify）"></a>1️⃣ 代码压缩（Minify）</h3><p>例如：<br>JavaScript<br>function calculateTotal(price) {<br>return price * 1.2;<br>}<br>压缩后：<br>JavaScript<br>function a(b){return b*1.2}<br>👉 这时候没有 source map，你基本无法调试</p><hr><h3 id="2️⃣-代码转译（Transpile）"><a href="#2️⃣-代码转译（Transpile）" class="headerlink" title="2️⃣ 代码转译（Transpile）"></a>2️⃣ 代码转译（Transpile）</h3><p>例如：</p><ul><li>ES6 → ES5（Babel）</li><li>JSX → JS（React）</li><li>Vue 模板 → JS<br>👉 转译前后代码已经不一样了</li></ul><hr><h3 id="3️⃣-打包工具（Webpack-Vite-Rollup）"><a href="#3️⃣-打包工具（Webpack-Vite-Rollup）" class="headerlink" title="3️⃣ 打包工具（Webpack &#x2F; Vite &#x2F; Rollup）"></a>3️⃣ 打包工具（Webpack &#x2F; Vite &#x2F; Rollup）</h3><p>最终上线的 JS：</p><ul><li>可能是多个文件合并</li><li>变量名可能被重命名</li><li>代码结构已经改变</li></ul><hr><h2 id="三、JS-和-TS-在-map-上的区别"><a href="#三、JS-和-TS-在-map-上的区别" class="headerlink" title="三、JS 和 TS 在 .map 上的区别"></a>三、JS 和 TS 在 <code>.map</code> 上的区别</h2><h3 id="TypeScript"><a href="#TypeScript" class="headerlink" title="TypeScript"></a>TypeScript</h3><ul><li>必然涉及编译（TS → JS）</li><li><code>.map</code> 几乎是“标配”</li></ul><hr><h3 id="JavaScript"><a href="#JavaScript" class="headerlink" title="JavaScript"></a>JavaScript</h3><ul><li>原生 JS：<strong>不需要 <code>.map</code></strong></li><li>但一旦经过工具链（Babel &#x2F; Webpack 等）：<br>👉 <strong>同样需要 <code>.map</code></strong></li></ul><hr><h2 id="四、一个更本质的理解"><a href="#四、一个更本质的理解" class="headerlink" title="四、一个更本质的理解"></a>四、一个更本质的理解</h2><p>你可以这样理解：</p><blockquote><p><code>.map</code> 不是给 TypeScript 用的，而是给“被改变过的代码”用的。<br>只要发生了这些事情：</p></blockquote><ul><li>压缩（minify）</li><li>转译（transpile）</li><li>打包（bundle）<br>👉 就可能需要 source map</li></ul><hr><h2 id="五、一个现实对比"><a href="#五、一个现实对比" class="headerlink" title="五、一个现实对比"></a>五、一个现实对比</h2><p>场景<br>TS<br>JS<br>直接写原生代码<br>❌（不能运行）<br>✔<br>需要编译<br>✔ 必须<br>❌（可选）<br>需要 <code>.map</code><br>✔ 强烈建议<br>✔（现代项目几乎都会用）</p><hr><h2 id="六、什么时候-JS-不需要-map？"><a href="#六、什么时候-JS-不需要-map？" class="headerlink" title="六、什么时候 JS 不需要 .map？"></a>六、什么时候 JS 不需要 <code>.map</code>？</h2><p>只有一种情况：</p><blockquote><p><strong>你写的是“未经过任何处理的原生 JavaScript”，并且直接运行</strong><br>例如：<br>HTML<br></span><br><span>  </span><span class=ͼe>console</span><span class=ͼ8>.</span><span>log(</span><span class=ͼc>“hello”</span><span>);</span><br><span class=ͼg><br>👉 这种情况下：</p></blockquote><ul><li>没有编译</li><li>没有压缩</li><li>没有打包<br>→ <code>.map</code> 没意义</li></ul><hr><h2 id="七、一句话总结-1"><a href="#七、一句话总结-1" class="headerlink" title="七、一句话总结"></a>七、一句话总结</h2><blockquote><p><strong>Source Map 的本质是：帮助你在“被改造过的代码”和“原始代码”之间建立映射，而 TypeScript 只是最常见的使用场景之一。</strong></p></blockquote><hr><p>如果你愿意，我可以帮你画一张“从 TS&#x2F;JS → 打包 → <code>.map</code> → 调试”的完整链路图，这个对理解整个前端构建体系会很有帮助。</p><h4 id="你说：-5"><a href="#你说：-5" class="headerlink" title="你说："></a>你说：</h4><p>在你提供的如下例子中，保留”原始信息 -&gt; 编译后信息”的具体数据结构和文件是什么样的，也是和TypeScript同样的”.map”文件吗？ ES6 → ES5（Babel） JSX → JS（React） Vue 模板 → JS<br>2&#x2F;2</p><h4 id="ChatGPT-说：-5"><a href="#ChatGPT-说：-5" class="headerlink" title="ChatGPT 说："></a>ChatGPT 说：</h4><p>可以直说：<strong>是的，本质上就是同一类东西——Source Map（<code>.map</code> 文件）</strong>。<br>不管是 TypeScript 还是 Babel &#x2F; React &#x2F; Vue，它们用的都是同一种标准机制来做“原始代码 ↔ 编译后代码”的映射。</p><hr><h2 id="一、统一结论"><a href="#一、统一结论" class="headerlink" title="一、统一结论"></a>一、统一结论</h2><p>你提到的这些转换：</p><ul><li>ES6 → ES5（Babel）</li><li>JSX → JS（React）</li><li>Vue 模板 → JS<br>👉 都会（或可以）生成 <strong>标准的 Source Map 文件</strong>：<blockquote><p><strong><code>.js.map</code>（或 <code>.map</code>）文件</strong><br>而这个文件的结构是<strong>统一规范的</strong>（不是各家自定义的）。</p></blockquote></li></ul><hr><h2 id="二、这个“映射文件”长什么样？"><a href="#二、这个“映射文件”长什么样？" class="headerlink" title="二、这个“映射文件”长什么样？"></a>二、这个“映射文件”长什么样？</h2><p>所有工具（Babel、TypeScript、Vue 编译器）最终都遵循同一个规范，大致结构如下：<br>JSON<br>{<br>“version”: 3,<br>“file”: “app.js”,<br>“sources”: [“app.jsx”],<br>“names”: [“calculateTotal”],<br>“mappings”: “AAAA,SAASA…”<br>}<br>如果更完整一点，还可能有：<br>JSON<br>{<br>“version”: 3,<br>“file”: “bundle.js”,<br>“sources”: [“src&#x2F;App.jsx”, “src&#x2F;utils.js”],<br>“sourcesContent”: [“原始 JSX 代码…”],<br>“names”: [“Component”, “render”],<br>“mappings”: “…”<br>}</p><hr><h2 id="三、不同工具的“区别”在哪？"><a href="#三、不同工具的“区别”在哪？" class="headerlink" title="三、不同工具的“区别”在哪？"></a>三、不同工具的“区别”在哪？</h2><p>虽然<strong>最终产物都是 <code>.map</code> 文件</strong>，但来源不同：</p><h3 id="1️⃣-TypeScript"><a href="#1️⃣-TypeScript" class="headerlink" title="1️⃣ TypeScript"></a>1️⃣ TypeScript</h3><ul><li>由 TypeScript 编译器生成</li><li>映射 TS → JS</li></ul><hr><h3 id="2️⃣-Babel（ES6-→-ES5-JSX-→-JS）"><a href="#2️⃣-Babel（ES6-→-ES5-JSX-→-JS）" class="headerlink" title="2️⃣ Babel（ES6 → ES5 &#x2F; JSX → JS）"></a>2️⃣ Babel（ES6 → ES5 &#x2F; JSX → JS）</h3><ul><li>Babel 也支持生成 source map<br>配置示例：<br>JavaScript<br>{<br>“presets”: [“@babel&#x2F;preset-env”, “@babel&#x2F;preset-react”],<br>“sourceMaps”: true<br>}<br>👉 输出：</li><li>ES5 JS</li><li><code>.js.map</code></li></ul><hr><h3 id="3️⃣-Vue（模板-→-JS）"><a href="#3️⃣-Vue（模板-→-JS）" class="headerlink" title="3️⃣ Vue（模板 → JS）"></a>3️⃣ Vue（模板 → JS）</h3><p>Vue 编译器（如 <code>vue-loader</code>）：</p><ul><li>会把 <code>.vue</code> 模板编译成 render 函数</li><li>同样生成 source map</li></ul><hr><h2 id="四、关键点：不是“谁的专属格式”"><a href="#四、关键点：不是“谁的专属格式”" class="headerlink" title="四、关键点：不是“谁的专属格式”"></a>四、关键点：不是“谁的专属格式”</h2><p>👉 很重要的一点：</p><blockquote><p><strong>Source Map 是一个标准规范（不是 TypeScript 专属，也不是 Babel 专属）</strong><br>只要工具支持，就会生成<strong>同一种格式</strong>。</p></blockquote><hr><h2 id="五、Source-Map-内部是怎么工作的（核心理解）"><a href="#五、Source-Map-内部是怎么工作的（核心理解）" class="headerlink" title="五、Source Map 内部是怎么工作的（核心理解）"></a>五、Source Map 内部是怎么工作的（核心理解）</h2><p><code>.map</code> 里有一个关键字段：</p><h3 id="mappings"><a href="#mappings" class="headerlink" title="mappings"></a><code>mappings</code></h3><p>它是一个<strong>压缩编码字符串</strong>，描述：<br>压缩后的 JS位置  →  原始源码位置<br>例如：<br>AAAA,SAASA<br>👉 实际含义是：<br>bundle.js:10:5  →  src&#x2F;App.jsx:3:1</p><hr><h2 id="六、再强调一个你容易忽略的点"><a href="#六、再强调一个你容易忽略的点" class="headerlink" title="六、再强调一个你容易忽略的点"></a>六、再强调一个你容易忽略的点</h2><p>不同工具<strong>可以决定要不要生成 sourceContent</strong>：</p><h3 id="有些-map："><a href="#有些-map：" class="headerlink" title="有些 .map："></a>有些 <code>.map</code>：</h3><p>JSON<br>“sourcesContent”: [“完整源码”]<br>👉 可直接还原源码（敏感）</p><hr><h3 id="有些-map：-1"><a href="#有些-map：-1" class="headerlink" title="有些 .map："></a>有些 <code>.map</code>：</h3><p>JSON<br>“sources”: [“src&#x2F;App.jsx”]<br>👉 只提供路径，不含源码</p><hr><h2 id="七、总结（帮你收敛认知）"><a href="#七、总结（帮你收敛认知）" class="headerlink" title="七、总结（帮你收敛认知）"></a>七、总结（帮你收敛认知）</h2><p>你可以这样记：</p><blockquote><p><strong>TypeScript、Babel、Vue 这些工具只是“生产 JS”，而 <code>.map</code> 是它们共同遵守的一种“调试用说明书”。</strong></p></blockquote><ul><li>数据结构：✅ 统一（Source Map 规范）</li><li>文件形式：✅ <code>.map</code></li><li>来源：❗ 各工具生成</li><li>目的：👉 把“编译后代码”还原回“原始代码”</li></ul><hr><p>如果你想更深入一点，我可以帮你拆一层更底层的东西：<br>👉 <code>mappings</code> 字符串到底是怎么编码行列信息的（VLQ 编码），这是很多人第一次看到会直接“看不懂但很神奇”的部分。</p><h4 id="你说：-6"><a href="#你说：-6" class="headerlink" title="你说："></a>你说：</h4><p>你所说的Source Map标准规范是谁制定的，何时制定的，当时制定的初衷是什么？</p><h4 id="ChatGPT-说：-6"><a href="#ChatGPT-说：-6" class="headerlink" title="ChatGPT 说："></a>ChatGPT 说：</h4><p>Source Map 不是某个“语言组织”制定的，而是由浏览器厂商和前端工具链共同推动形成的一个标准，核心来自 <strong>Google</strong>，后来被纳入社区标准并广泛实现。</p><hr><h2 id="一、是谁制定的？"><a href="#一、是谁制定的？" class="headerlink" title="一、是谁制定的？"></a>一、是谁制定的？</h2><p>最初的推动者是：</p><ul><li><strong>Google</strong><br>由 Google 的开发者（主要是为 Chrome DevTools 设计）提出并实现<br>随后：</li><li><strong>ECMAScript 社区（TC39）</strong></li><li>各大浏览器厂商（Chrome、Firefox 等）</li><li>工具链（Babel、TypeScript、Webpack 等）<br>共同逐步形成“事实标准”<br>👉 可以理解为：<strong>Google 起头 + 社区共识 + 工具生态跟进</strong></li></ul><hr><h2 id="二、什么时候制定的？"><a href="#二、什么时候制定的？" class="headerlink" title="二、什么时候制定的？"></a>二、什么时候制定的？</h2><p>Source Map 的出现时间大约在：</p><blockquote><p><strong>2010 年左右</strong><br>当时正值：</p></blockquote><ul><li>前端开始复杂化（模块化、框架化）</li><li>代码开始被压缩、合并</li><li>开发与生产代码出现严重“脱节”</li></ul><hr><h2 id="三、为什么要制定它？（核心初衷）"><a href="#三、为什么要制定它？（核心初衷）" class="headerlink" title="三、为什么要制定它？（核心初衷）"></a>三、为什么要制定它？（核心初衷）</h2><p>一句话总结：</p><blockquote><p><strong>让“开发时的代码”和“运行时的代码”重新建立可追踪关系。</strong><br>在 Source Map 出现之前，前端调试面临几个痛点：</p></blockquote><hr><h3 id="1️⃣-压缩代码不可读"><a href="#1️⃣-压缩代码不可读" class="headerlink" title="1️⃣ 压缩代码不可读"></a>1️⃣ 压缩代码不可读</h3><p>JavaScript<br>function a(b){return b*1.2}<br>👉 调试基本等于“猜谜”</p><hr><h3 id="2️⃣-堆栈信息不可用"><a href="#2️⃣-堆栈信息不可用" class="headerlink" title="2️⃣ 堆栈信息不可用"></a>2️⃣ 堆栈信息不可用</h3><p>报错只会告诉你：<br>app.min.js:1:84321<br>👉 对开发者几乎没用</p><hr><h3 id="3️⃣-转译代码断层"><a href="#3️⃣-转译代码断层" class="headerlink" title="3️⃣ 转译代码断层"></a>3️⃣ 转译代码断层</h3><p>随着这些技术出现：</p><ul><li>TypeScript</li><li>CoffeeScript（历史）</li><li>JSX</li><li>代码压缩工具<br>👉 代码经历了“多次变形”<br>结果是：<blockquote><p><strong>你写的代码 ≠ 浏览器执行的代码</strong></p></blockquote></li></ul><hr><h2 id="四、Source-Map-解决了什么问题？"><a href="#四、Source-Map-解决了什么问题？" class="headerlink" title="四、Source Map 解决了什么问题？"></a>四、Source Map 解决了什么问题？</h2><p>它本质上做了一件事：</p><blockquote><p><strong>记录“每一段编译后代码，来自原始代码的哪里”</strong><br>从而实现：</p></blockquote><ul><li>调试直接看到 TS &#x2F; JSX &#x2F; 原始 JS</li><li>错误定位到源码行号</li><li>断点调试“还原”源码</li></ul><hr><h2 id="五、一个很关键的设计目标"><a href="#五、一个很关键的设计目标" class="headerlink" title="五、一个很关键的设计目标"></a>五、一个很关键的设计目标</h2><p>Source Map 的设计目标非常克制：</p><blockquote><p><strong>只解决“映射问题”，不改变运行逻辑</strong><br>它不会：</p></blockquote><ul><li>修改代码执行方式</li><li>影响性能（除了加载 map 文件）</li><li>改变语言本身<br>👉 它只是一个“辅助信息文件”</li></ul><hr><h2 id="六、为什么能成为“事实标准”？"><a href="#六、为什么能成为“事实标准”？" class="headerlink" title="六、为什么能成为“事实标准”？"></a>六、为什么能成为“事实标准”？</h2><p>因为它解决的是<strong>全行业共性问题</strong>，而不是某个框架的问题：</p><ul><li>所有前端构建工具都需要它</li><li>所有浏览器调试器都支持它</li><li>所有语言编译到 JS 的工具都能接入它</li></ul><hr><h2 id="七、一句话总结-2"><a href="#七、一句话总结-2" class="headerlink" title="七、一句话总结"></a>七、一句话总结</h2><blockquote><p>Source Map 是由 Google 在约 2010 年提出的，用于解决“压缩&#x2F;编译后代码难以调试”的问题，随后被浏览器和前端工具链共同采纳，逐步成为事实上的行业标准。</p></blockquote><hr><p>如果你感兴趣，我可以再往下讲一层更“底层”的内容：<br>👉 为什么 Source Map 要用 VLQ 编码（以及它是怎么做到高效压缩映射数据的）——这个是很多工程师第一次接触时最“反直觉”的地方。</p><h4 id="你说：-7"><a href="#你说：-7" class="headerlink" title="你说："></a>你说：</h4><p>如果原始的代码经过了多次转化，比如第一次转换是”TypeScript-&gt; Javascript”，第二次转换是”Javascript -&gt; 混淆后的Javascript”，这两次转换会生成不同的source map吗？</p><h4 id="ChatGPT-说：-7"><a href="#ChatGPT-说：-7" class="headerlink" title="ChatGPT 说："></a>ChatGPT 说：</h4><p>是的——<strong>通常会生成两份不同的 Source Map</strong>，而且这在实际工程里是标准做法。</p><hr><h2 id="一、你的这个链路可以这样理解"><a href="#一、你的这个链路可以这样理解" class="headerlink" title="一、你的这个链路可以这样理解"></a>一、你的这个链路可以这样理解</h2><p>你描述的是两步转换：<br>1️⃣ <code>TypeScript → JavaScript</code><br>2️⃣ <code>JavaScript → 混淆 / 压缩后的 JavaScript</code><br>👉 每一步“变形”，理论上都可以有一份对应的 source map。</p><hr><h2 id="二、为什么需要两份-map？"><a href="#二、为什么需要两份-map？" class="headerlink" title="二、为什么需要两份 map？"></a>二、为什么需要两份 map？</h2><p>因为每一层都在做不同的事情：<br>阶段<br>输入<br>输出<br>Source Map<br>第一步<br>TS<br>JS<br>TS → JS 的 map<br>第二步<br>JS<br>压缩 JS<br>JS → 压缩 JS 的 map<br>👉 每个 map 只负责<strong>自己这一步的映射</strong></p><hr><h2 id="三、但实际调试是怎么“串起来”的？"><a href="#三、但实际调试是怎么“串起来”的？" class="headerlink" title="三、但实际调试是怎么“串起来”的？"></a>三、但实际调试是怎么“串起来”的？</h2><p>关键点来了：<strong>Source Map 可以“叠加”使用（source map chaining）</strong></p><hr><h3 id="情况一：只有第二步-map（理想但不完整）"><a href="#情况一：只有第二步-map（理想但不完整）" class="headerlink" title="情况一：只有第二步 map（理想但不完整）"></a>情况一：只有第二步 map（理想但不完整）</h3><p>压缩 JS → JS（未压缩）<br>👉 你只能看到“未压缩 JS”，但看不到 TS</p><hr><h3 id="情况二：有完整链路（推荐）"><a href="#情况二：有完整链路（推荐）" class="headerlink" title="情况二：有完整链路（推荐）"></a>情况二：有完整链路（推荐）</h3><p>现代工具会把两个 map 串起来：<br>压缩 JS<br>↓（map2）<br>原始 JS<br>↓（map1）<br>TS<br>👉 最终效果：</p><blockquote><p>你可以直接在浏览器看到 TS 代码调试</p></blockquote><hr><h2 id="四、关键机制：Source-Map-的“嵌套引用”"><a href="#四、关键机制：Source-Map-的“嵌套引用”" class="headerlink" title="四、关键机制：Source Map 的“嵌套引用”"></a>四、关键机制：Source Map 的“嵌套引用”</h2><p>第二个 map（压缩后）通常会这样写：<br>JavaScript<br>&#x2F;&#x2F;# sourceMappingURL&#x3D;app.min.js.map<br>而这个 map 里面的 <code>sources</code> 可能指向：<br>JSON<br>“sources”: [“app.js”]<br>👉 而 <code>app.js</code> 本身可能也有自己的 <code>.map</code></p><hr><h2 id="五、那工具是怎么处理“多层-map”的？"><a href="#五、那工具是怎么处理“多层-map”的？" class="headerlink" title="五、那工具是怎么处理“多层 map”的？"></a>五、那工具是怎么处理“多层 map”的？</h2><p>现代构建工具（Webpack、Vite、Rollup）会：<br>👉 <strong>自动“合并&#x2F;消费” source map</strong><br>也就是：</p><ul><li>不需要你手动处理多层 map</li><li>工具会帮你把 TS → JS → minified JS 串起来</li></ul><hr><h2 id="六、一个更现实的情况（很重要）"><a href="#六、一个更现实的情况（很重要）" class="headerlink" title="六、一个更现实的情况（很重要）"></a>六、一个更现实的情况（很重要）</h2><p>在实际项目中，<strong>你通常只看到一个 <code>.map</code> 文件</strong><br>👉 因为：</p><h3 id="🔥-构建工具会“消费上一层-map，再生成新的-map”"><a href="#🔥-构建工具会“消费上一层-map，再生成新的-map”" class="headerlink" title="🔥 构建工具会“消费上一层 map，再生成新的 map”"></a>🔥 构建工具会“消费上一层 map，再生成新的 map”</h3><p>例如：<br>1️⃣ TypeScript 输出：<br>app.js + app.js.map（TS → JS）<br>2️⃣ Webpack 再处理：<br>bundle.js + bundle.js.map（直接从 TS 映射）<br>👉 结果：</p><blockquote><p>你最终只需要一个“最终 map”</p></blockquote><hr><h2 id="七、关键总结"><a href="#七、关键总结" class="headerlink" title="七、关键总结"></a>七、关键总结</h2><h3 id="✔-理论上："><a href="#✔-理论上：" class="headerlink" title="✔ 理论上："></a>✔ 理论上：</h3><blockquote><p>每一次代码转换都可以生成自己的 source map</p></blockquote><hr><h3 id="✔-实际上："><a href="#✔-实际上：" class="headerlink" title="✔ 实际上："></a>✔ 实际上：</h3><blockquote><p>多个 source map 会被<strong>链式组合（chained）</strong>，最终表现为“从最终代码直接映射回原始代码”</p></blockquote><hr><h3 id="✔-工程实践："><a href="#✔-工程实践：" class="headerlink" title="✔ 工程实践："></a>✔ 工程实践：</h3><blockquote><p>你通常只需要关心“最终产物的 source map”，工具会自动处理中间层</p></blockquote><hr><h2 id="八、一句话总结"><a href="#八、一句话总结" class="headerlink" title="八、一句话总结"></a>八、一句话总结</h2><blockquote><p>多次代码转换确实会产生多个 source map，但现代构建工具会把它们自动串联起来，让你在调试时可以“一步到位”看到最原始的源码。</p></blockquote><hr><p>如果你愿意，我可以帮你画一张“多层 source map 链路图”（从 TS → JS → minified JS → 浏览器调试）的结构图，这个对理解构建工具的工作原理会非常直观。</p><h4 id="你说：-8"><a href="#你说：-8" class="headerlink" title="你说："></a>你说：</h4><p>这种source map是Javascript语言生态所独有的吗？其他语言生态，如Java&#x2F;Python是否存在类似的概念？</p><h4 id="ChatGPT-说：-8"><a href="#ChatGPT-说：-8" class="headerlink" title="ChatGPT 说："></a>ChatGPT 说：</h4><p>不是 JavaScript 生态独有的，但<strong>Source Map 这一“具体标准格式”基本是前端世界的产物</strong>。<br>在其他语言里，确实有类似“映射原始源码”的概念，但实现方式、形式都不一样。<br>我们可以这样理解：</p><blockquote><p><strong>理念是通用的，但实现和标准不通用。</strong></p></blockquote><hr><h2 id="一、JavaScript-生态：Source-Map（统一标准）"><a href="#一、JavaScript-生态：Source-Map（统一标准）" class="headerlink" title="一、JavaScript 生态：Source Map（统一标准）"></a>一、JavaScript 生态：Source Map（统一标准）</h2><p>在 JS&#x2F;TS 世界中：</p><ul><li>Source Map 是一个<strong>统一规范</strong></li><li>被浏览器（Chrome、Firefox）和工具链（Babel、TS、Webpack）共同支持<br>👉 特点：</li><li>文件形式：<code>.map</code></li><li>标准统一</li><li>浏览器原生支持</li></ul><hr><h2 id="二、Java-JVM-生态：调试信息-符号表"><a href="#二、Java-JVM-生态：调试信息-符号表" class="headerlink" title="二、Java &#x2F; JVM 生态：调试信息 + 符号表"></a>二、Java &#x2F; JVM 生态：调试信息 + 符号表</h2><p>在 Java 里，没有 Source Map 这种统一 <code>.map</code> 文件，但有类似机制：</p><h3 id="1️⃣-调试信息（Debug-Symbols）"><a href="#1️⃣-调试信息（Debug-Symbols）" class="headerlink" title="1️⃣ 调试信息（Debug Symbols）"></a>1️⃣ 调试信息（Debug Symbols）</h3><p>Java 编译后：</p><ul><li><code>.class</code> 文件里可以包含调试信息</li><li>包括：</li><li>原始文件名</li><li>行号</li><li>变量名<br>👉 作用类似 source map：<blockquote><p>把字节码映射回 <code>.java</code></p></blockquote></li></ul><hr><h3 id="2️⃣-JVM-的设计优势"><a href="#2️⃣-JVM-的设计优势" class="headerlink" title="2️⃣ JVM 的设计优势"></a>2️⃣ JVM 的设计优势</h3><p>JVM 本身就支持：</p><ul><li>保留方法名</li><li>保留类名</li><li>保留行号映射<br>所以：<br>👉 Java 调试体验“天生就很好”，不像 JS 那样依赖额外文件</li></ul><hr><h3 id="3️⃣-反编译（另一种“映射”）"><a href="#3️⃣-反编译（另一种“映射”）" class="headerlink" title="3️⃣ 反编译（另一种“映射”）"></a>3️⃣ 反编译（另一种“映射”）</h3><p>工具如：</p><ul><li><strong>JD-GUI</strong><br>可以：<blockquote><p>从 <code>.class</code> 反推出 Java 代码（接近原始）<br>但这不是官方标准机制，而是“逆向工程”。</p></blockquote></li></ul><hr><h2 id="三、Python：更接近“原生可见源码”"><a href="#三、Python：更接近“原生可见源码”" class="headerlink" title="三、Python：更接近“原生可见源码”"></a>三、Python：更接近“原生可见源码”</h2><p>Python 的情况更特殊：</p><h3 id="1️⃣-直接运行源码"><a href="#1️⃣-直接运行源码" class="headerlink" title="1️⃣ 直接运行源码"></a>1️⃣ 直接运行源码</h3><p>Python 是解释型语言：<br>Python<br>运行<br>print(“hello”)<br>👉 没有强制编译过程<br>👉 源码本身就是运行代码</p><hr><h3 id="2️⃣-pyc（字节码）"><a href="#2️⃣-pyc（字节码）" class="headerlink" title="2️⃣ .pyc（字节码）"></a>2️⃣ <code>.pyc</code>（字节码）</h3><p>Python 会生成：</p><ul><li><code>.pyc</code>（字节码）<br>但：</li><li><code>.pyc</code> 仍然保留很多结构信息</li><li>可以反编译（如工具 <strong>uncompyle6</strong>）<br>👉 但：<blockquote><p>没有一个像 Source Map 那样的“官方映射文件”</p></blockquote></li></ul><hr><h2 id="四、其他语言也有类似思想"><a href="#四、其他语言也有类似思想" class="headerlink" title="四、其他语言也有类似思想"></a>四、其他语言也有类似思想</h2><h3 id="1️⃣-C-C-：调试符号（DWARF）"><a href="#1️⃣-C-C-：调试符号（DWARF）" class="headerlink" title="1️⃣ C &#x2F; C++：调试符号（DWARF）"></a>1️⃣ C &#x2F; C++：调试符号（DWARF）</h3><ul><li>使用 <strong>DWARF 调试信息</strong></li><li>存在于 <code>.debug</code> 区段或 <code>.pdb</code> 文件（Windows）<br>👉 用于：</li><li>行号映射</li><li>变量名</li><li>调试器（GDB &#x2F; LLDB）</li></ul><hr><h3 id="2️⃣-NET-C"><a href="#2️⃣-NET-C" class="headerlink" title="2️⃣ .NET &#x2F; C#"></a>2️⃣ .NET &#x2F; C#</h3><ul><li>使用 <strong>PDB（Program Database）文件</strong><br>👉 作用类似：</li><li>源码 ↔ IL 代码映射</li></ul><hr><h3 id="3️⃣-Go"><a href="#3️⃣-Go" class="headerlink" title="3️⃣ Go"></a>3️⃣ Go</h3><ul><li>内置调试信息</li><li>支持直接映射源码（但不是独立 <code>.map</code> 文件）</li></ul><hr><h2 id="五、对比总结（很关键）"><a href="#五、对比总结（很关键）" class="headerlink" title="五、对比总结（很关键）"></a>五、对比总结（很关键）</h2><p>生态<br>是否有 Source Map<br>实现方式<br>JavaScript<br>✅ 有（标准 <code>.map</code>）<br>Source Map<br>Java<br>❌ 没有统一 <code>.map</code><br>JVM 调试信息<br>Python<br>❌ 没有统一 <code>.map</code><br>字节码 + 可反编译<br>C&#x2F;C++<br>❌<br>DWARF &#x2F; debug symbols<br>.NET<br>❌<br>PDB</p><hr><h2 id="六、核心本质（真正重要的理解）"><a href="#六、核心本质（真正重要的理解）" class="headerlink" title="六、核心本质（真正重要的理解）"></a>六、核心本质（真正重要的理解）</h2><p>你可以这样抽象：</p><blockquote><p><strong>所有语言都需要解决：运行代码 ≠ 原始代码的问题</strong><br>只是：</p></blockquote><ul><li>JS → 用 <code>.map</code></li><li>Java → 用 JVM 调试信息</li><li>Python → 几乎不用（或靠反编译）</li><li>C&#x2F;C++ → 用 DWARF</li></ul><hr><h2 id="七、一句话总结-3"><a href="#七、一句话总结-3" class="headerlink" title="七、一句话总结"></a>七、一句话总结</h2><blockquote><p>Source Map 不是唯一的解决方案，也不是 JS 独有的需求；它只是 JavaScript 生态中最标准、最显式的一种“源码映射实现”，而其他语言用的是各自体系内的调试&#x2F;符号机制来解决同样的问题。</p></blockquote><hr><p>如果你愿意，我可以帮你深入对比一件更有意思的事：<br>👉 为什么 Java &#x2F; Python 很少需要像 Source Map 这样的“额外文件”，而 JS 却强依赖它——这里面其实是语言设计哲学的差异。</p><h4 id="你说：-9"><a href="#你说：-9" class="headerlink" title="你说："></a>你说：</h4><p>请讲</p><h4 id="ChatGPT-说：-9"><a href="#ChatGPT-说：-9" class="headerlink" title="ChatGPT 说："></a>ChatGPT 说：</h4><p>这个问题的核心，其实不在“有没有 Source Map”，而在于：<strong>语言本身的执行模型和运行环境设计</strong>不同。<br>我直接把关键差异讲透，不绕弯。</p><hr><h2 id="一、JavaScript：被“编译之后才运行”的语言"><a href="#一、JavaScript：被“编译之后才运行”的语言" class="headerlink" title="一、JavaScript：被“编译之后才运行”的语言"></a>一、JavaScript：被“编译之后才运行”的语言</h2><p>现代前端里的 JavaScript（尤其是配合 TS、Babel、打包工具）：<br>👉 实际流程是：<br>源码（TS &#x2F; JSX &#x2F; ES6）<br>↓（多次转换）<br>编译后的 JS<br>↓（压缩&#x2F;混淆）<br>最终 JS<br>↓<br>浏览器执行<br>关键点：</p><blockquote><p><strong>浏览器运行的代码 ≠ 你写的代码</strong><br>所以必须有：<br>👉 <strong>一个“翻译说明书”来把运行时代码映射回源码</strong><br>→ 这就是 Source Map</p></blockquote><hr><h2 id="二、Java-Python：设计上“更接近源码运行”"><a href="#二、Java-Python：设计上“更接近源码运行”" class="headerlink" title="二、Java &#x2F; Python：设计上“更接近源码运行”"></a>二、Java &#x2F; Python：设计上“更接近源码运行”</h2><h3 id="1️⃣-Java：JVM-统一执行模型"><a href="#1️⃣-Java：JVM-统一执行模型" class="headerlink" title="1️⃣ Java：JVM 统一执行模型"></a>1️⃣ Java：JVM 统一执行模型</h3><p>Java 的结构是：<br>.java → .class → JVM执行<br>但重点是：<br>👉 JVM 设计之初就考虑了调试需求：</p><ul><li>行号信息 ✔</li><li>方法名 ✔</li><li>类结构 ✔<br>这些信息<strong>直接内建在字节码里</strong><br>👉 所以：<blockquote><p>JVM 本身就自带“源码映射能力”，不需要额外的 Source Map 文件</p></blockquote></li></ul><hr><h3 id="2️⃣-Python：几乎是“源码直跑”"><a href="#2️⃣-Python：几乎是“源码直跑”" class="headerlink" title="2️⃣ Python：几乎是“源码直跑”"></a>2️⃣ Python：几乎是“源码直跑”</h3><p>Python：<br>.py → 解释执行 &#x2F; .pyc<br>特点：</p><ul><li>不需要强制编译步骤</li><li>代码结构非常接近执行模型<br>👉 也就是说：<blockquote><p><strong>源码本身就是“第一等公民”</strong><br>所以：</p></blockquote></li><li>不需要一个独立的映射系统</li><li>调试时直接就是源码级别</li></ul><hr><h2 id="三、关键差异：是否“隐藏源码”"><a href="#三、关键差异：是否“隐藏源码”" class="headerlink" title="三、关键差异：是否“隐藏源码”"></a>三、关键差异：是否“隐藏源码”</h2><p>这是本质区别👇</p><h3 id="JavaScript（前端）"><a href="#JavaScript（前端）" class="headerlink" title="JavaScript（前端）"></a>JavaScript（前端）</h3><blockquote><p>浏览器只看到“编译后的 JS”<br>所以：</p></blockquote><ul><li>你的源码是“被隐藏的”</li><li>调试时需要“反向映射”<br>👉 Source Map 是“还原工具”</li></ul><hr><h3 id="Java-Python"><a href="#Java-Python" class="headerlink" title="Java &#x2F; Python"></a>Java &#x2F; Python</h3><blockquote><p>运行环境本身就“理解源码结构”</p></blockquote><ul><li>Java：通过 JVM 记录调试信息</li><li>Python：几乎直接运行源码<br>👉 不需要额外“还原”</li></ul><hr><h2 id="四、为什么-JS-生态必须有-Source-Map？"><a href="#四、为什么-JS-生态必须有-Source-Map？" class="headerlink" title="四、为什么 JS 生态必须有 Source Map？"></a>四、为什么 JS 生态必须有 Source Map？</h2><p>这是前端特有的现实问题：</p><h3 id="1️⃣-浏览器是“黑盒执行环境”"><a href="#1️⃣-浏览器是“黑盒执行环境”" class="headerlink" title="1️⃣ 浏览器是“黑盒执行环境”"></a>1️⃣ 浏览器是“黑盒执行环境”</h3><ul><li>你不能修改浏览器执行模型</li><li>浏览器只认 JS</li></ul><hr><h3 id="2️⃣-前端必须经过构建流程"><a href="#2️⃣-前端必须经过构建流程" class="headerlink" title="2️⃣ 前端必须经过构建流程"></a>2️⃣ 前端必须经过构建流程</h3><p>现代前端几乎一定会：</p><ul><li>TS 编译</li><li>JSX 转换</li><li>模块打包</li><li>代码压缩<br>👉 每一步都“破坏了源码结构”</li></ul><hr><h3 id="3️⃣-代码压缩是默认行为"><a href="#3️⃣-代码压缩是默认行为" class="headerlink" title="3️⃣ 代码压缩是默认行为"></a>3️⃣ 代码压缩是默认行为</h3><p>生产环境：<br>JavaScript<br>function a(b){return b*1.2}<br>👉 没 Source Map：</p><ul><li>调试 ≈ 猜谜游戏</li></ul><hr><h2 id="五、本质差异总结（重点）"><a href="#五、本质差异总结（重点）" class="headerlink" title="五、本质差异总结（重点）"></a>五、本质差异总结（重点）</h2><p>可以这样理解：</p><h3 id="JavaScript："><a href="#JavaScript：" class="headerlink" title="JavaScript："></a>JavaScript：</h3><blockquote><p><strong>源码 → 多次变形 → 才能运行</strong><br>所以必须：<br>👉 用 Source Map 把“变形路径”记录下来</p></blockquote><hr><h3 id="Java-Python："><a href="#Java-Python：" class="headerlink" title="Java &#x2F; Python："></a>Java &#x2F; Python：</h3><blockquote><p><strong>源码 → 运行环境直接理解</strong><br>所以：<br>👉 不需要一个独立的“映射文件”</p></blockquote><hr><h2 id="六、一个更底层的哲学差异"><a href="#六、一个更底层的哲学差异" class="headerlink" title="六、一个更底层的哲学差异"></a>六、一个更底层的哲学差异</h2><h3 id="JS（前端）"><a href="#JS（前端）" class="headerlink" title="JS（前端）"></a>JS（前端）</h3><blockquote><p>运行环境不可控（浏览器）<br>👉 所以要靠工具链补救<br>👉 Source Map 是“补丁”</p></blockquote><hr><h3 id="Java-Python-1"><a href="#Java-Python-1" class="headerlink" title="Java &#x2F; Python"></a>Java &#x2F; Python</h3><blockquote><p>运行环境可控（JVM &#x2F; 解释器）<br>👉 从设计上就支持调试<br>👉 映射能力“内建”</p></blockquote><hr><h2 id="七、打个直观比喻"><a href="#七、打个直观比喻" class="headerlink" title="七、打个直观比喻"></a>七、打个直观比喻</h2><ul><li>JavaScript：<br>👉 你写的是中文<br>👉 机器只能看英文<br>👉 Source Map &#x3D; 翻译词典</li></ul><hr><ul><li>Java &#x2F; Python：<br>👉 机器本身就会中文<br>👉 不需要翻译</li></ul><hr><h2 id="八、一句话收尾"><a href="#八、一句话收尾" class="headerlink" title="八、一句话收尾"></a>八、一句话收尾</h2><blockquote><p><strong>Source Map 的存在，本质上是因为 JavaScript 在运行时“失去了原始代码语义”，而 Java 和 Python 通过运行时设计保留了这些语义，所以不依赖独立的映射文件。</strong></p></blockquote><hr><p>如果你愿意，我可以再帮你讲一个更“硬核”的问题：<br>👉 为什么 JS 不像 Java 一样把调试信息直接塞进运行代码里，而是选择用外部 <code>.map</code> 文件？这背后其实是“性能 + 安全 + 架构”的权衡。</p><h4 id="你说：-10"><a href="#你说：-10" class="headerlink" title="你说："></a>你说：</h4><p>请讲</p><h4 id="ChatGPT-说：-10"><a href="#ChatGPT-说：-10" class="headerlink" title="ChatGPT 说："></a>ChatGPT 说：</h4><p>这个问题已经触及到前端架构设计的“取舍本质”了。直接讲结论：</p><blockquote><p><strong>JavaScript 选择把 Source Map 放在“外部文件”，而不是像 Java 一样内嵌调试信息，是在性能、体积、安全、以及运行环境限制之间做的权衡。</strong><br>我们分几层拆开讲清楚。</p></blockquote><hr><h2 id="一、先对比两种设计"><a href="#一、先对比两种设计" class="headerlink" title="一、先对比两种设计"></a>一、先对比两种设计</h2><h3 id="Java（JVM-体系）"><a href="#Java（JVM-体系）" class="headerlink" title="Java（JVM 体系）"></a>Java（JVM 体系）</h3><ul><li>调试信息可以直接嵌在 <code>.class</code> 里</li><li>JVM 运行时可以读取这些信息</li><li>调试能力是“运行时自带”的<br>👉 特点：<strong>内建 + 紧耦合</strong></li></ul><hr><h3 id="JavaScript（浏览器生态）"><a href="#JavaScript（浏览器生态）" class="headerlink" title="JavaScript（浏览器生态）"></a>JavaScript（浏览器生态）</h3><ul><li>Source Map 是<strong>独立文件</strong></li><li>通过：<br>JavaScript<br>&#x2F;&#x2F;# sourceMappingURL&#x3D;app.js.map<br>被“引用”<br>👉 特点：<strong>外部 + 松耦合</strong></li></ul><hr><h2 id="二、为什么-JS-不直接内嵌？"><a href="#二、为什么-JS-不直接内嵌？" class="headerlink" title="二、为什么 JS 不直接内嵌？"></a>二、为什么 JS 不直接内嵌？</h2><h3 id="1️⃣-文件体积-性能问题（最现实）"><a href="#1️⃣-文件体积-性能问题（最现实）" class="headerlink" title="1️⃣ 文件体积 &amp; 性能问题（最现实）"></a>1️⃣ 文件体积 &amp; 性能问题（最现实）</h3><p>如果把 Source Map 信息直接塞进 JS：</p><ul><li>JS 文件会变得非常大</li><li>每次加载都会多传一堆调试数据<br>👉 但生产环境：<blockquote><p>用户根本不需要调试信息<br>所以：<br>✔ 把它拆出去 &#x3D; 按需使用<br>✔ 不影响线上加载性能</p></blockquote></li></ul><hr><h2 id="三、2️⃣-浏览器执行模型限制"><a href="#三、2️⃣-浏览器执行模型限制" class="headerlink" title="三、2️⃣ 浏览器执行模型限制"></a>三、2️⃣ 浏览器执行模型限制</h2><p>浏览器（Chrome、Safari 等）：</p><ul><li>只执行 JS</li><li>并不会主动“理解 Source Map”<br>👉 Source Map 是给：</li><li>DevTools</li><li>调试器<br>而不是给 JS 引擎本身<br>所以：<blockquote><p><strong>调试信息和执行逻辑必须解耦</strong><br>否则浏览器需要：</p></blockquote></li><li>解析额外格式</li><li>增加复杂度<br>👉 这对性能和实现都不友好</li></ul><hr><h2 id="四、3️⃣-安全-信息暴露问题（非常关键）"><a href="#四、3️⃣-安全-信息暴露问题（非常关键）" class="headerlink" title="四、3️⃣ 安全 &amp; 信息暴露问题（非常关键）"></a>四、3️⃣ 安全 &amp; 信息暴露问题（非常关键）</h2><p>如果 Source Map 内嵌在 JS 里：<br>👉 所有人都能看到：</p><ul><li>原始代码</li><li>变量名</li><li>业务逻辑<br>而 JS 是：<blockquote><p><strong>直接发到用户浏览器的</strong><br>所以：<br>👉 你没法控制“谁能看到调试信息”</p></blockquote></li></ul><hr><h3 id="现在的方案（更安全）"><a href="#现在的方案（更安全）" class="headerlink" title="现在的方案（更安全）"></a>现在的方案（更安全）</h3><p>你可以：</p><ul><li>本地 &#x2F; 内部系统：允许访问 <code>.map</code></li><li>公网环境：禁止访问 <code>.map</code><br>👉 这就实现了：<blockquote><p><strong>调试能力 ≠ 对所有用户开放</strong></p></blockquote></li></ul><hr><h2 id="五、4️⃣-按需加载（非常重要的设计）"><a href="#五、4️⃣-按需加载（非常重要的设计）" class="headerlink" title="五、4️⃣ 按需加载（非常重要的设计）"></a>五、4️⃣ 按需加载（非常重要的设计）</h2><p>Source Map 是：</p><ul><li>DevTools 用的时候才加载</li><li>不用的时候完全不加载<br>👉 优势：</li><li>不影响页面加载速度</li><li>不影响用户体验<br>如果内嵌：<blockquote><p>所有用户都会被迫加载调试信息</p></blockquote></li></ul><hr><h2 id="六、5️⃣-构建工具链的复杂性"><a href="#六、5️⃣-构建工具链的复杂性" class="headerlink" title="六、5️⃣ 构建工具链的复杂性"></a>六、5️⃣ 构建工具链的复杂性</h2><p>现代前端：</p><ul><li>TS → Babel → Webpack → 压缩<br>👉 每一步都可能生成 source map<br>如果强制内嵌：</li><li>工具之间必须深度耦合</li><li>每一步都要“理解上一层的调试信息”<br>👉 这会让工具链非常复杂</li></ul><hr><h2 id="七、6️⃣-浏览器-DevTools-的设计选择"><a href="#七、6️⃣-浏览器-DevTools-的设计选择" class="headerlink" title="七、6️⃣ 浏览器 DevTools 的设计选择"></a>七、6️⃣ 浏览器 DevTools 的设计选择</h2><p>浏览器 DevTools 采用的是：</p><blockquote><p><strong>“按需解析外部 Source Map”</strong><br>而不是：<br>“强制内嵌调试数据”<br>原因：</p></blockquote><ul><li>更灵活</li><li>不影响主线程执行</li><li>可以懒加载</li></ul><hr><h2 id="八、一个非常关键的设计哲学"><a href="#八、一个非常关键的设计哲学" class="headerlink" title="八、一个非常关键的设计哲学"></a>八、一个非常关键的设计哲学</h2><p>你可以把两种模式理解为：</p><h3 id="Java："><a href="#Java：" class="headerlink" title="Java："></a>Java：</h3><blockquote><p><strong>“调试能力是运行时的一部分”</strong></p></blockquote><hr><h3 id="JavaScript：-1"><a href="#JavaScript：-1" class="headerlink" title="JavaScript："></a>JavaScript：</h3><blockquote><p><strong>“调试能力是开发工具的一部分”</strong></p></blockquote><hr><h2 id="九、一句话总结"><a href="#九、一句话总结" class="headerlink" title="九、一句话总结"></a>九、一句话总结</h2><blockquote><p>Java 把调试信息“内建在运行环境里”，而 JavaScript 把调试信息“放在外部工具体系中”，这是为了在浏览器这个不可控、对性能敏感、且面向终端用户的环境中实现更灵活、更安全、更高效的调试方案。</p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;After I knew “&lt;a href=&quot;https://www.reddit.com/r/ClaudeAI/comments/1s8ifm6/claude_code_source_code_has_been_leaked_via_a_map/&quot;&gt;Claude code</summary>
      
    
    
    
    
    <category term="javascript" scheme="https://gangmax.me/tags/javascript/"/>
    
    <category term="chatgpt" scheme="https://gangmax.me/tags/chatgpt/"/>
    
  </entry>
  
  <entry>
    <title>VolcEngine</title>
    <link href="https://gangmax.me/blog/2026/03/11/volcengine/"/>
    <id>https://gangmax.me/blog/2026/03/11/volcengine/</id>
    <published>2026-03-11T18:37:54.000Z</published>
    <updated>2026-04-17T07:25:55.196Z</updated>
    
    <content type="html"><![CDATA[<p>“VolcEngine(火山引擎)” is ByteDance’s AI platform, which provides API for third parties to use. Of cource you need to pay for it.</p><h3 id="Add-Credit"><a href="#Add-Credit" class="headerlink" title="Add Credit"></a>Add Credit</h3><p>Go to “<a href="https://console.volcengine.com/finance/account-overview">here</a>“, type the ammount such as “10” and tap “充值汇款”, then you can scan the displayed barcode and use “Alipay&#x2F;Wechatpay” to recharge.</p><h3 id="Model-List"><a href="#Model-List" class="headerlink" title="Model List"></a>Model List</h3><p>Go to “<a href="https://console.volcengine.com/home">home</a> -&gt; <a href="https://console.volcengine.com/ark/region:ark+cn-beijing/overview">火山方舟</a> -&gt; <a href="https://console.volcengine.com/ark/region:ark+cn-beijing/openManagement">开通管理</a>“, you can see all the models you can use.</p><h3 id="Response-API-example"><a href="#Response-API-example" class="headerlink" title="Response API example"></a>Response API example</h3><p>Go to “<a href="https://console.volcengine.com/home">home</a> -&gt; <a href="https://console.volcengine.com/ark/region:ark+cn-beijing/overview">火山方舟</a> -&gt; <a href="https://console.volcengine.com/ark/region:ark+cn-beijing/openManagement">开通管理</a>“, select a model in the list, such as “<a href="https://console.volcengine.com/ark/region:ark+cn-beijing/model/detail?Id=doubao-seed-1-8">Doubao-Seed-1.8</a>“, tap “API 接入”，it will tell you have to use this model with an API call, such as:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">curl https://ark.cn-beijing.volces.com/api/v3/responses \</span><br><span class="line">-H &quot;Authorization: Bearer $ARK_API_KEY&quot; \</span><br><span class="line">-H &#x27;Content-Type: application/json&#x27; \</span><br><span class="line">-d &#x27;&#123;</span><br><span class="line">    &quot;model&quot;: &quot;doubao-seed-1-8-251228&quot;,</span><br><span class="line">    &quot;input&quot;: [</span><br><span class="line">        &#123;</span><br><span class="line">            &quot;role&quot;: &quot;user&quot;,</span><br><span class="line">            &quot;content&quot;: [</span><br><span class="line">                &#123;</span><br><span class="line">                    &quot;type&quot;: &quot;input_image&quot;,</span><br><span class="line">                    &quot;image_url&quot;: &quot;https://oss.mxiqi.com/V1/AUC/20260301/IMG/2c24eefd37661&quot;</span><br><span class="line">                &#125;,</span><br><span class="line">                &#123;</span><br><span class="line">                    &quot;type&quot;: &quot;input_image&quot;,</span><br><span class="line">                    &quot;image_url&quot;: &quot;https://oss.mxiqi.com/V1/AUC/20260301/IMG/37591a998ae9c&quot;</span><br><span class="line">                &#125;,                &#123;</span><br><span class="line">                    &quot;type&quot;: &quot;input_text&quot;,</span><br><span class="line">                    &quot;text&quot;: &quot;这是什么？目前的价格大概是多少？&quot;</span><br><span class="line">                &#125;</span><br><span class="line">            ]</span><br><span class="line">        &#125;</span><br><span class="line">    ]</span><br></pre></td></tr></table></figure><p>You need to replace the “$ARK_API_KEY” part with your API key before running.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;“VolcEngine(火山引擎)” is ByteDance’s AI platform, which provides API for third parties to use. Of cource you need to pay for it.&lt;/p&gt;
&lt;h3 id=</summary>
      
    
    
    
    
    <category term="ai" scheme="https://gangmax.me/tags/ai/"/>
    
  </entry>
  
  <entry>
    <title>The Frustrating Seller on Xianyu</title>
    <link href="https://gangmax.me/blog/2026/03/04/the-frustrating-seller-on-xianyu/"/>
    <id>https://gangmax.me/blog/2026/03/04/the-frustrating-seller-on-xianyu/</id>
    <published>2026-03-04T17:23:24.000Z</published>
    <updated>2026-04-17T07:25:55.196Z</updated>
    
    <content type="html"><![CDATA[<p>On February 25th, I purchased an item from a seller on the “Xianyu” app for over 2,000 RMB. From the other items she was selling, this seller appeared to be a young woman. Before placing the order, I had a decent conversation with her while asking about the details of the product. However, after I made the payment and placed the order, I asked her when the item would be shipped, and she said, “In about five days, I’m currently out of town.” Well, I can understand that she might be away during the holiday season(Chinese New Year), but shouldn’t she have informed me about this before I placed the order? Given her unreliable behavior, I had reason to doubt whether she would actually ship the item on the promised day. So, I asked her: “If it’s five days later, then it would be March 2nd, a Monday. Can you confirm?” She replied with 2 words: “Pretty much.” From this, it was clear that she had no empathy, as if the world should revolve around her. At that point, I pretty much knew what kind of person I was dealing with. So, I decided not to waste any more time arguing with her and just waited until March 2nd.</p><p>On that day, the item wasn’t shipped, and she didn’t send me any messages. On the morning of March 3rd, I messaged her asking why she hadn’t shipped it yet, and she said, “I couldn’t find the item, I’ll look for it when I get home tonight.” I was completely speechless. What could I even say at that point? From March 3rd to the morning of March 4th, I still received no updates. By 2 p.m. on March 4th, I messaged her again, asking, “Did you find it?” but still no reply. At that point, I immediately applied for a refund.</p><p>Around 4 p.m., her message finally came: “Sorry, I didn’t see your message because I’ve been busy working.” This “sorry” was really hard to come by. If we break it down, she probably should have apologized to me several times by now. I even wonder if I hadn’t applied for a refund, she might never have bothered to say “sorry.” She then mentioned that she still hadn’t found the item, but at this point, it didn’t matter anymore. I asked her to approve the refund immediately because I didn’t want to waste any more seconds on someone like this.</p><p>So, what did I learn from this shopping experience?</p><ol><li><p>Before making a purchase, don’t assume the seller is a reasonable person(sadly). Ask as many questions as possible, like when the item will be shipped, to avoid potential pitfalls.</p></li><li><p>The “seller reputation” numbers on Xianyu is just for reference (this seller had a 97% positive rating with zero disputes). A low rating is obviously a red flag, while a high rating doesn’t guarantee anything either.</p></li><li><p>In a society where overall social standards may still be lacking, no matter how much you try to prevent issues, there’s a high chance you’ll encounter people with poor manners. It’s necessary to be vigilant, but it’s also important to relax yourself to accept this fact. After all, what else can you do?</p></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;On February 25th, I purchased an item from a seller on the “Xianyu” app for over 2,000 RMB. From the other items she was selling, this se</summary>
      
    
    
    
    
    <category term="record" scheme="https://gangmax.me/tags/record/"/>
    
    <category term="thought" scheme="https://gangmax.me/tags/thought/"/>
    
  </entry>
  
  <entry>
    <title>Mathematics Content in Markdown</title>
    <link href="https://gangmax.me/blog/2025/11/18/mathematics-content-in-markdown/"/>
    <id>https://gangmax.me/blog/2025/11/18/mathematics-content-in-markdown/</id>
    <published>2025-11-18T17:30:59.000Z</published>
    <updated>2026-04-17T07:25:55.196Z</updated>
    
    <content type="html"><![CDATA[<h3 id="Mathematics-Diagram"><a href="#Mathematics-Diagram" class="headerlink" title="Mathematics Diagram"></a>Mathematics Diagram</h3><ol><li><p>Use the online version of “<a href="https://www.mathcha.io/editor">Mathcha</a>“.</p></li><li><p>Use the “<a href="https://www.geogebra.org/">GeoGebra</a>“ application which is available on “macOS&#x2F;iOS”.</p></li></ol><h3 id="Mathematics-Equation-Markdown-LateX"><a href="#Mathematics-Equation-Markdown-LateX" class="headerlink" title="Mathematics Equation(Markdown + LateX)"></a>Mathematics Equation(Markdown + LateX)</h3><h4 id="Plan-A-write-LaTeX-in-markdown-content-directly"><a href="#Plan-A-write-LaTeX-in-markdown-content-directly" class="headerlink" title="Plan A: write LaTeX in markdown content directly"></a>Plan A: write LaTeX in markdown content directly</h4><ol><li><p>Install MacTeX</p><p> MacTeX is required by the “pandoc” command below. So please install it from <a href="https://www.tug.org/mactex/mactex-download.html">this</a>, or install the basic version(which is smaller and I installed this version) from <a href="https://www.tug.org/mactex/morepackages.html">this</a>.</p></li><li><p>Use the following command to convert “markdown + Latex” into HTML file(from <a href="https://stackoverflow.com/a/55106932/3115617">this</a>):</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Export to HTML.</span></span><br><span class="line">pandoc --standalone --mathjax -f markdown -t html sample.md -o sample.html</span><br><span class="line"></span><br><span class="line"><span class="comment"># Export to PDF(it seems the full version of &quot;MacTeX&quot; is required by this,</span></span><br><span class="line"><span class="comment"># otherwise the Chinese characters cannot be displayed properly).</span></span><br><span class="line"><span class="comment"># You may be prompted &quot;weasyprint not found. Please select a different </span></span><br><span class="line"><span class="comment"># --pdf-engine or install weasyprint&quot;, if so, install it first with</span></span><br><span class="line"><span class="comment"># &quot;brew install weasyprint&quot;.</span></span><br><span class="line">pandoc sample.md --pdf-engine=xelatex -V CJKmainfont=<span class="string">&quot;PingFang SC&quot;</span> \</span><br><span class="line">--from=markdown+raw_html --resource-path=.:svg \</span><br><span class="line">-V geometry:margin=1.5cm -o sample.pdf</span><br></pre></td></tr></table></figure></li><li><p>Use the online tool “<a href="https://latex2image.joeraut.com/">latex2image-web</a>“ to convert a given LaTeX expression to local image(SVG&#x2F;PNG) file.</p></li></ol><h4 id="Plan-B-use-online-“markdown-LaTeX”-editor"><a href="#Plan-B-use-online-“markdown-LaTeX”-editor" class="headerlink" title="Plan B: use online “markdown+LaTeX” editor"></a>Plan B: use online “markdown+LaTeX” editor</h4><p>Use the online editor “<a href="https://upmath.me/">upmath</a>“ to write the markdown, and render the generated HTML content into PDF with the printing feature provided by browser like Chrome.</p><h3 id="Content-of-“sample-md”"><a href="#Content-of-“sample-md”" class="headerlink" title="Content of “sample.md”"></a>Content of “sample.md”</h3><p>Here is the file content used in the commands above. The “.&#x2F;svg&#x2F;equation.svg” file is an equation image made with “<a href="https://latex2image.joeraut.com/">latex2image-web</a>“.</p><figure class="highlight markdown"><figcaption><span>sample.md</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># Abstract</span></span><br><span class="line"></span><br><span class="line">From [<span class="string">there</span>](<span class="link">https://goessner.github.io/mdmath/publication.html</span>).</span><br><span class="line"></span><br><span class="line">Euler&#x27;s identity makes a valid formula out of five mathematical constants.</span><br><span class="line"></span><br><span class="line"><span class="section">## 1. Introduction</span></span><br><span class="line"></span><br><span class="line">Euler&#x27;s identity is often cited as an example of deep mathematical beauty.</span><br><span class="line">Three basic arithmetic operations occur exactly once and combine five fundamental mathematical constants [<span class="string">[1</span>](<span class="link">#1</span>)].</span><br><span class="line"></span><br><span class="line"><span class="section">## 2. The Identity</span></span><br><span class="line"></span><br><span class="line">这是一句中文，用于测试中文环境下使用Latex表达式是否正常，比如: $\Delta = b^2 - 4ac$</span><br><span class="line"></span><br><span class="line">这是第二句中文，用于测试中文环境下使用Latex表达式是否正常，比如: ![](<span class="link">./svg/equation.svg</span>)</span><br><span class="line"></span><br><span class="line">Starting from Euler&#x27;s formula $$e^&#123;ix&#125;=\cos x + i\sin x$$ for any real number $x$, we get to Euler&#x27;s identity with the special case of $x = \pi$</span><br><span class="line"></span><br><span class="line">$$e^&#123;i\pi&#125;+1=0\,.$$ (1)</span><br><span class="line"></span><br><span class="line">The arithmetic operations <span class="emphasis">*addition*</span>, <span class="emphasis">*multiplication*</span> and <span class="emphasis">*exponentiation*</span> combine the fundamental constants</span><br><span class="line"></span><br><span class="line"><span class="bullet">*</span> the additive identity $0$.</span><br><span class="line"><span class="bullet">*</span> the multiplicative identity $1$.</span><br><span class="line"><span class="bullet">*</span> the circle constant $\pi$.</span><br><span class="line"><span class="bullet">*</span> Euler&#x27;s number $e$.</span><br><span class="line"><span class="bullet">*</span> the imaginary constant $i$.</span><br><span class="line"></span><br><span class="line">Add 2 more equations.</span><br><span class="line"></span><br><span class="line">$$S = \pi r^2$$</span><br><span class="line"></span><br><span class="line">$$e^&#123;i\pi&#125; + 1 = \lim<span class="emphasis">_&#123;n \to \infty&#125;\frac&#123;1&#125;&#123;n&#125;$$</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">## 3. Conclusion</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">It has been shown, how Euler&#x27;s identity makes a valid formula from five mathematical constants.</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">### References</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">1. [<span class="string">Euler&#x27;s identity</span>](<span class="link">https://en.wikipedia.org/wiki/Euler%27s_identity</span>)</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;Mathematics-Diagram&quot;&gt;&lt;a href=&quot;#Mathematics-Diagram&quot; class=&quot;headerlink&quot; title=&quot;Mathematics Diagram&quot;&gt;&lt;/a&gt;Mathematics Diagram&lt;/h3&gt;&lt;ol&gt;
</summary>
      
    
    
    
    
    <category term="markdown" scheme="https://gangmax.me/tags/markdown/"/>
    
    <category term="latex" scheme="https://gangmax.me/tags/latex/"/>
    
    <category term="mathematics" scheme="https://gangmax.me/tags/mathematics/"/>
    
  </entry>
  
  <entry>
    <title>About The Chinese Translation of &quot;Constitutional Monarchy&quot;</title>
    <link href="https://gangmax.me/blog/2025/11/13/about-the-chinese-translation-of-constitutional-monarchy/"/>
    <id>https://gangmax.me/blog/2025/11/13/about-the-chinese-translation-of-constitutional-monarchy/</id>
    <published>2025-11-13T15:03:23.000Z</published>
    <updated>2026-04-17T07:25:55.196Z</updated>
    
    <content type="html"><![CDATA[<p>当我知道中文里的“君主立宪制”对应的英语是“constitutional monarchy”之后，我一直觉得“君主立宪制”这个翻译十分欠妥，甚至有误导的嫌疑。</p><p>从字面意思看，“constitutional monarchy”的直译是“宪法的君主制&#x2F;宪政式君主制”或简称“宪法君主制&#x2F;宪政君主制”。在一百多年前，如果“宪法&#x2F;宪政”这样的词语还没有诞生或者流传不广(虽然我觉得不太可能)，那也可以翻译成“立宪君主制”。而“君主立宪制”这个翻译看着就很怪，因为它既不符合直觉也不符合逻辑：直白翻译成“立宪君主制”不好吗？如果不好，它是哪里比不上“君主立宪制”这个翻译呢？</p><p>从含义的角度来看就更奇怪了：“君主立宪”是一个主谓结构，强调的是“立宪”这个动作；而“立宪君主”是一个偏正结构，强调的是处于“宪政”的一种状态。这两者有非常重要的差别，完全不是一个意思。</p><p>想象一下，作为一个不懂英语的中国人，当看到“君主立宪制”这个词时，你的第一反应是不是认为它的核心是“立宪”这个动作？换句话说，作为“君主”，做到了“立宪”这个动作，这个制度也就建立起来了，对吗？</p><p>而“constitutional monarchy”(“宪政式君主制”)说的是，有宪法仅仅是第一步，更重要的是：“要以遵循宪法的方式进行君主统治”。这不是一个“有了宪法就成功”的动作，而是需要统治者一直保持一种“君主遵循宪法统治”的状态。也就是说：即便有了宪法，如果君主没有遵循宪法进行统治，那它依然不能被称为“constitutional monarchy”。</p><p>你看出这两者之间的区别了吗？</p><p>回想戊戌变法“百日维新”期间，那些变法者急吼吼地“立法”，仿佛有了这些条文，“constitutional monarchy”就建立起来了。这让我不禁怀疑，当年那些人是不是被“君主立宪制”这个中文翻译误导了？</p><p>我又猜测，“君主立宪制”这个词是否来自日语对“constitutional monarchy”的翻译，进而误导了我们的先辈们。我向“<a href="https://www.doubao.com/">豆包</a>”提出这个问题，豆包给出的答案是：“constitutional monarchy”在日语中通常被翻译为“立憲君主制（りっけんきんしゅせい）”。呃……看来这个锅还真没法甩到日本人头上，日本人的翻译没有犯这个错误。</p><p>到了这一步我很想知道，当年是什么人给出了如此歧义的翻译？我又问了“豆包”，它给出的答案是：“一般认为梁启超是将‘constitutional monarchy’翻译为‘君主立宪制’的关键人物”。</p><p>如果这是真的，我忍不住从阴谋论的角度想：梁任公当年这么翻译，是不是在给当时的君主和国人降低难度？让大家误以为：这个事情就是把规矩立起来就好、而不是把权力装进笼子里这么难？是不是生怕知道了真相的人们会直接撂挑子不干了？10分难度的事情咱们现在做不到，那就先把难度降低到3分，然后告诉大家：你们看，这件事情吧，其实好像也没有那么难，你们尽管去试。岂知大家真的试了一下，发现根本不是那么回事儿，于是失去了耐心，直接掀了桌子。</p><p>至此，大家明白学外语的重要性了吧？</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;当我知道中文里的“君主立宪制”对应的英语是“constitutional monarchy”之后，我一直觉得“君主立宪制”这个翻译十分欠妥，甚至有误导的嫌疑。&lt;/p&gt;
&lt;p&gt;从字面意思看，“constitutional monarchy”的直译是“宪法的君主制&amp;#x2F;宪</summary>
      
    
    
    
    
    <category term="record" scheme="https://gangmax.me/tags/record/"/>
    
    <category term="thought" scheme="https://gangmax.me/tags/thought/"/>
    
  </entry>
  
  <entry>
    <title>Freedom of Speech</title>
    <link href="https://gangmax.me/blog/2025/09/19/freedom-of-speech/"/>
    <id>https://gangmax.me/blog/2025/09/19/freedom-of-speech/</id>
    <published>2025-09-19T10:25:18.000Z</published>
    <updated>2026-04-17T07:25:55.195Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Question"><a href="#Question" class="headerlink" title="Question"></a>Question</h2><blockquote><p>美国的所谓“言论自由”是代表什么言论都可以自由发表吗？如果不是，哪些言论是不能自由发表的？如果存在这样的言论，该如何理解受到限制的所谓的“自由”？</p></blockquote><h2 id="Answer-from-“ChatGPT“"><a href="#Answer-from-“ChatGPT“" class="headerlink" title="Answer from “ChatGPT“"></a>Answer from “<a href="https://chatgpt.com/">ChatGPT</a>“</h2><p>好问题 👍。美国的“言论自由”确实是个容易被误解的概念。</p><p>在美国宪法第一修正案里，言论自由主要是指：政府不能随意限制或惩罚公民的表达（无论是文字、口头还是象征性的行为，比如焚烧国旗）。换句话说，它主要保护你免受政府的压制。</p><p>但这并不意味着什么话都可以随便说，它并不是“绝对自由”。美国最高法院已经明确划定了一些不受第一修正案保护的言论类别，包括：</p><ol><li><p><strong>直接煽动即将发生的违法行为（incitement</strong> 比如在暴乱中喊“冲进去砸店！现在！”——这是违法的。</p></li><li><p><strong>真实威胁（true threats）</strong> 明确的、可信的暴力威胁（如“我明天要杀了你”）。</p></li><li><p><strong>诽谤、诬蔑（defamation）</strong> 散布虚假事实、恶意中伤他人名誉会被起诉。</p></li><li><p><strong>淫秽言论（obscenity）</strong> 这是个很窄的定义，不是单纯的色情，而是缺乏艺术、政治或社会价值、并且过度露骨的内容。</p></li><li><p><strong>欺诈与虚假商业广告</strong> 比如虚假宣传、诈骗广告。</p></li><li><p><strong>部分敏感场景下的言论</strong> 在学校：学生的言论可以在一定范围内被限制。在军队、监狱：言论自由受到更严格约束。</p></li></ol><p><strong>那么，“受限的自由”该怎么理解？</strong></p><p>关键在于：</p><p>**核心价值言论（political speech）**几乎总是受到最强保护的，即便它很极端、很不受欢迎。</p><p><strong>限制</strong>主要是针对会直接造成<strong>具体、立即、严重危害</strong>的言论，而不是因为“冒犯了谁”就被禁止。</p><p>所以，美国的“言论自由”更像是：</p><p>👉 <strong>只要不直接威胁公共安全、他人权利或触发法律明确禁止的类别，你就有权利说。</strong></p><p>👉 但这不等于你说了就没人会反驳、没人会封号、没人会抵制。社会和私人平台（比如推特、脸书）可以对你进行限制，这不算政府侵犯你的言论自由。</p><p>要打个比方：</p><p>在美国，政府不能因为你喊“我讨厌总统”就抓你。</p><p>但如果你喊“我要明天去白宫炸掉总统办公室”，那就完了。</p><p>而如果你在推特上骂总统，推特觉得你违反平台规则，把你号封了——那是推特的“自由”，不是政府的行为。</p><h2 id="Answer-from-“Grok“"><a href="#Answer-from-“Grok“" class="headerlink" title="Answer from “Grok“"></a>Answer from “<a href="https://grok.com/">Grok</a>“</h2><p>美国的言论自由主要受宪法第一修正案保护，该修正案规定国会不得制定法律剥夺言论自由、出版自由等权利。这项权利旨在保护个人表达观点、批评政府和参与公共辩论的能力，但它并非绝对的、无条件的“任何言论都可以自由发表”。下面我将逐一回答你的问题，并基于可靠来源提供解释。</p><h4 id="言论自由是否代表任何言论都可以自由发表？"><a href="#言论自由是否代表任何言论都可以自由发表？" class="headerlink" title="言论自由是否代表任何言论都可以自由发表？"></a>言论自由是否代表任何言论都可以自由发表？</h4><p>不是。美国最高法院多次裁定，言论自由有界限。它保护大多数表达形式，包括政治言论、艺术、宗教观点和商业广告，但某些言论被视为有害或超出保护范围，会受到法律限制。这些限制是为了平衡个人权利与公共安全、社会秩序或其他宪法权利（如隐私权、平等权）。例如，政府不能基于内容审查大多数言论，但可以对某些类别施加合理限制。</p><h4 id="哪些言论是不能自由发表的？"><a href="#哪些言论是不能自由发表的？" class="headerlink" title="哪些言论是不能自由发表的？"></a>哪些言论是不能自由发表的？</h4><p>以下是美国法律中常见的言论自由例外。这些例外基于历史判例和联邦&#x2F;州法律，通常由法院判定是否适用。注意，这些不是 exhaustive 列表，且具体情况需视上下文而定（如在线言论可能涉及额外平台政策）。我将它们分类列出，并简要说明理由：</p><ul><li><p><strong>煽动违法行为（Incitement）</strong>：如果言论旨在煽动或导致立即的违法行为，且很可能发生（如呼吁暴力起义），则不受保护。经典判例：Brandenburg v. Ohio (1969)，要求言论必须“直接煽动”且“迫在眉睫”。</p></li><li><p><strong>真威胁（True Threats）</strong>：对他人发出真实的伤害威胁，不受保护。例如，威胁杀害某人或炸弹威胁。判例：Virginia v. Black (2003)，强调意图和上下文。</p></li><li><p><strong>淫秽材料（Obscenity）</strong>：严重色情且无艺术&#x2F;科学价值的材料不受保护。标准来自Miller v. California (1973)：必须诉诸淫秽兴趣、描绘性行为且整体缺乏严肃价值。但成人色情（如色情小说）通常受保护，除非涉及儿童。</p></li><li><p><strong>儿童色情（Child Pornography）</strong>：任何描绘未成年人的性行为材料均非法，不受第一修正案保护。判例：New York v. Ferber (1982)。这包括虚拟或AI生成的图像。</p></li><li><p><strong>诽谤和中伤（Defamation）</strong>：虚假陈述损害他人名誉，可被起诉（民事而非刑事）。公职人员需证明“实际恶意”（New York Times v. Sullivan, 1964）。这不禁止意见表达，但禁止故意散布谎言。</p></li><li><p><strong>战斗性言语（Fighting Words）</strong>：直接面对面侮辱可能引发暴力反应的言语不受保护。判例：Chaplinsky v. New Hampshire (1942)，但现代法院很少适用此例外。</p></li><li><p><strong>商业言论限制</strong>：虚假或误导性广告可被規制（如FDA对药品广告的管制），但纯商业言论受部分保护。</p></li><li><p><strong>时间、地点和方式限制（Time, Place, and Manner Restrictions）</strong>：政府可对言论施加中性限制，如禁止在学校大声喧哗或限制游行路线，但不能基于内容歧视。</p></li></ul><p>此外，在特定环境中（如学校、监狱、工作场所）言论自由更受限。例如，学生言论可被学校限制如果干扰教育（Tinker v. Des Moines, 1969）。仇恨言论通常受保护，除非落入上述类别。</p><p>这些限制主要通过刑事起诉、民事诉讼或内容移除（如社交媒体平台自愿执行）来实施。私营公司（如Twitter或Facebook）不受第一修正案约束，可自行设定规则。</p><h4 id="如何理解受到限制的所谓的“自由”？"><a href="#如何理解受到限制的所谓的“自由”？" class="headerlink" title="如何理解受到限制的所谓的“自由”？"></a>如何理解受到限制的所谓的“自由”？</h4><p>美国的言论自由被视为“有限制的自由”（qualified freedom），而非绝对的。这反映了宪法设计：第一修正案保护言论以促进民主、真理发现和个人自治，但必须与其他价值（如公共安全、平等和秩序）平衡。最高法院常引用“在拥挤的剧院里喊‘火’”（falsely shouting fire in a crowded theater）作为比喻（源自Schenck v. United States, 1919），说明自由有界限以防止伤害。</p><p>理解方式：</p><ul><li><p><strong>实用主义视角</strong>：自由不是无政府状态，而是通过司法审查（如严格审查标准）确保限制是最小必要的。只有当言论造成清晰且现实的危害时，才可限制。这比许多国家（如中国或俄罗斯）的言论管制更宽松，美国的体系允许更多争议性表达（如政治异见或讽刺）。</p></li><li><p><strong>历史与演变</strong>：早期判例更宽松（如1919年允许限制反战言论），但20世纪中叶后趋向保护（如1960年代民权运动）。到2025年，随着数字时代，法院继续处理新挑战，如在线仇恨言论或AI生成内容，但核心原则未变。</p></li><li><p><strong>批评与辩论</strong>：一些人认为这些限制太松（如允许仇恨言论），另一些认为太严（如对校园言论的管制）。这体现了“自由”的动态性：它通过法院、社会规范和立法不断调整。</p></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;Question&quot;&gt;&lt;a href=&quot;#Question&quot; class=&quot;headerlink&quot; title=&quot;Question&quot;&gt;&lt;/a&gt;Question&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;美国的所谓“言论自由”是代表什么言论都可以自由发表吗？如果不是，哪</summary>
      
    
    
    
    
    <category term="record" scheme="https://gangmax.me/tags/record/"/>
    
  </entry>
  
  <entry>
    <title>Convert Images to PDF and Vice Versa</title>
    <link href="https://gangmax.me/blog/2025/09/12/convert-images-to-pdf-and-vice-versa/"/>
    <id>https://gangmax.me/blog/2025/09/12/convert-images-to-pdf-and-vice-versa/</id>
    <published>2025-09-12T11:02:55.000Z</published>
    <updated>2026-04-17T07:25:55.195Z</updated>
    
    <content type="html"><![CDATA[<p>The content below comes from ChatGPT’s answers.</p><h3 id="Images-to-PDF"><a href="#Images-to-PDF" class="headerlink" title="Images to PDF"></a>Images to PDF</h3><p>First method, you can use the command line tool “<a href="https://imagemagick.org/">ImageMagick</a>“.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Before running, make sure you image files are named in order,</span></span><br><span class="line"><span class="comment"># such as &quot;001.jpg&quot;, &quot;002.jpg&quot;, &quot;003.jpg&quot; and so on.</span></span><br><span class="line">magick *.jpg comic.pdf</span><br><span class="line"><span class="comment"># OR:</span></span><br><span class="line">magick *.png comic.pdf</span><br></pre></td></tr></table></figure><p>The second method, you can use Python script with the “<a href="https://pypi.org/project/pillow/">Pillow</a>“ library support.</p><figure class="highlight python"><figcaption><span>comic.py</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> PIL <span class="keyword">import</span> Image</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">convert</span>(<span class="params">image_folder: <span class="built_in">str</span>, target_pdf: <span class="built_in">str</span></span>) -&gt; <span class="literal">None</span>:</span><br><span class="line">    <span class="comment"># Get list of image files.</span></span><br><span class="line">    image_files = <span class="built_in">sorted</span>(</span><br><span class="line">        [f <span class="keyword">for</span> f <span class="keyword">in</span> os.listdir(image_folder) <span class="keyword">if</span> f.lower().endswith((<span class="string">&#x27;.jpg&#x27;</span>, <span class="string">&#x27;.png&#x27;</span>))]</span><br><span class="line">    )</span><br><span class="line">    images = []</span><br><span class="line">    <span class="keyword">for</span> file <span class="keyword">in</span> image_files:</span><br><span class="line">        img_path = os.path.join(image_folder, file)</span><br><span class="line">        img = Image.<span class="built_in">open</span>(img_path).convert(<span class="string">&#x27;RGB&#x27;</span>)</span><br><span class="line">        images.append(img)</span><br><span class="line">    <span class="comment"># Save all images to a single PDF.</span></span><br><span class="line">    <span class="keyword">if</span> images:</span><br><span class="line">        images[<span class="number">0</span>].save(target_pdf, save_all=<span class="literal">True</span>, append_images=images[<span class="number">1</span>:])</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">len</span>(sys.argv) &lt; <span class="number">3</span>:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;Usage: python comic.py &lt;image_folder&gt; &lt;target_pdf&gt;&#x27;</span>)</span><br><span class="line">        exit(<span class="number">0</span>)</span><br><span class="line">    convert(sys.argv[<span class="number">1</span>], sys.argv[<span class="number">2</span>])</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;Done.&#x27;</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="PDF-to-Images"><a href="#PDF-to-Images" class="headerlink" title="PDF to Images"></a>PDF to Images</h3><p>The easiest way seems to use the command line tool “pdfimages” provided by “<a href="https://poppler.freedesktop.org/">poppler</a>“.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Install &quot;poppler&quot; on macOS:</span></span><br><span class="line">brew install poppler</span><br><span class="line"><span class="comment"># Or on Debian/Ubuntu:</span></span><br><span class="line">sudo apt install poppler-utils</span><br><span class="line"><span class="comment"># Run the following commands to extract images from PDF file.</span></span><br><span class="line"><span class="comment"># The &quot;prefix&quot; value below will be used to name the extracted</span></span><br><span class="line"><span class="comment"># image file. With the command above, you will get image files</span></span><br><span class="line"><span class="comment"># with name like:</span></span><br><span class="line"><span class="comment">#   prefix-000.jpg</span></span><br><span class="line"><span class="comment">#   prefix-001.jpg</span></span><br><span class="line"><span class="comment">#   prefix-002.jpg</span></span><br><span class="line"><span class="comment">#   ...</span></span><br><span class="line">pdfimages -all comic.pdf prefix</span><br></pre></td></tr></table></figure><p>The questions I sent to ChatGPT were:</p><ol><li><p>How to convert a batch of comic image files in “jpg&#x2F;png” formats into a single PDF file?</p></li><li><p>Is there a way to extract images from a PDF file?</p></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;The content below comes from ChatGPT’s answers.&lt;/p&gt;
&lt;h3 id=&quot;Images-to-PDF&quot;&gt;&lt;a href=&quot;#Images-to-PDF&quot; class=&quot;headerlink&quot; title=&quot;Images to P</summary>
      
    
    
    
    
    <category term="pdf" scheme="https://gangmax.me/tags/pdf/"/>
    
    <category term="ebook" scheme="https://gangmax.me/tags/ebook/"/>
    
  </entry>
  
  <entry>
    <title>single-file-cli: &quot;Invalid parameters when calling Network.setCookies&quot;</title>
    <link href="https://gangmax.me/blog/2025/09/02/single-file-cli-invalid-parameters-when-calling-networksetcookies/"/>
    <id>https://gangmax.me/blog/2025/09/02/single-file-cli-invalid-parameters-when-calling-networksetcookies/</id>
    <published>2025-09-02T12:12:16.000Z</published>
    <updated>2026-04-17T07:25:55.195Z</updated>
    
    <content type="html"><![CDATA[<p>When using “single-file-cli” with a “cookies.json” file, I got the following error:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; ./single-file-aarch64-apple-darwin --browser-cookies-file=<span class="string">&quot;./cookies.json&quot;</span> https://... target.html</span><br><span class="line">Invalid parameters when calling Network.setCookies(&#123;<span class="string">&quot;cookies&quot;</span>:[&#123; ... &#125;]&#125;)</span><br></pre></td></tr></table></figure><p>The cookie JSON file was exported by a chrome <a href="https://cookie-editor.com/">extension</a> which contained the following content:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="string">&quot;domain&quot;</span>: <span class="string">&quot;.xyz.com&quot;</span>,</span><br><span class="line">        <span class="string">&quot;expirationDate&quot;</span>: <span class="number">1756787525.533015</span>,</span><br><span class="line">        <span class="string">&quot;hostOnly&quot;</span>: <span class="literal">false</span>,</span><br><span class="line">        <span class="string">&quot;httpOnly&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="string">&quot;name&quot;</span>: <span class="string">&quot;_esc_h38&quot;</span>,</span><br><span class="line">        <span class="string">&quot;path&quot;</span>: <span class="string">&quot;/&quot;</span>,</span><br><span class="line">        <span class="string">&quot;sameSite&quot;</span>: <span class="literal">null</span></span><br><span class="line">        <span class="string">&quot;secure&quot;</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="string">&quot;session&quot;</span>: <span class="literal">false</span>,</span><br><span class="line">        <span class="string">&quot;storeId&quot;</span>: <span class="literal">null</span>,</span><br><span class="line">        <span class="string">&quot;value&quot;</span>: <span class="string">&quot;5nNCJOP4Ss&quot;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    ...</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>ChatGPT said only the “name&#x2F;value&#x2F;domain&#x2F;path” fields were required which seemed correct. The “sameSite&#x2F;storeId” fields seemed suspect because of the “null” value. I removed these two fields and tried. The error didn’t show anymore. Not sure why the “Cookie-Editor” extension exports invalid field values but good to know the solution with the help from ChatGPT.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;When using “single-file-cli” with a “cookies.json” file, I got the following error:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td cla</summary>
      
    
    
    
    
    <category term="single-file-cli" scheme="https://gangmax.me/tags/single-file-cli/"/>
    
  </entry>
  
  <entry>
    <title>Ownership</title>
    <link href="https://gangmax.me/blog/2025/06/13/ownership/"/>
    <id>https://gangmax.me/blog/2025/06/13/ownership/</id>
    <published>2025-06-13T03:48:57.000Z</published>
    <updated>2026-04-17T07:25:55.195Z</updated>
    
    <content type="html"><![CDATA[<p>若干年前，我当时所在一家公司的领导会时不时地对我们说：我们在工作中要有ownership，要尽力承担更多的责任。按理说，这是一句很平常的话，很多领导都喜欢说。但很奇怪，他的这句话让我一直印象深刻。直到最近这两天，偶然间我想明白了其中的原因。</p><p>回到问题的本质，ownership到底是什么意思？</p><p>如果去问ChatGPT，它会给出如下的答案：</p><p>“Ownership” generally means having legal or rightful control over something — whether it’s a physical object, idea, responsibility, or even a situation.</p><p>这个回答和我想的差不多。我的理解是：ownership本质上是一种权利，即对某事物的支配和做决定的权利。明白了这一点就可以知道，那些强调“员工应该具有ownership”的企业所说的“ownership”根本不是这个意思，因为他们说这句话的时候显然并不是要让员工支配资源和做决定。他们想让员工做的是更多的工作，尤其是超出了日常要求的工作。这件事如果一定要用一个单词来表达的话，那responsibility其实更好(虽然也不完全合适)。然而有意思的是，企业更喜欢使用“ownership”这个词不达意的字眼来表达，其中的心思呼之欲出，这就是毫无争议的PUA。</p><p>我向ChatGPT说出了以上想法，它表示同意，并给出了以下判断“ture ownership”和”false ownership”的简单方法：</p><blockquote><ol><li>问自己：“我是否真的可以决定工作中的关键事情？”</li><li>问自己：“我是否得到足够资源和支持去完成这些责任？”</li><li>问自己：“我的付出是否被认可和合理回报？”</li><li>问自己：“我是否可以拒绝不合理的额外要求而不会被责备？”</li></ol><p>如果答案大多是否定的，那很可能你面对的是“false ownership”，甚至是职场PUA。</p></blockquote><p>我觉得ChatGPT给出的这个判断方法不错，打工人可以按照这个方法自行判断。</p><p>我想了一下，这件事之所以让我印象深刻，大部分原因要归结于这个领导是一个对待下属很真诚很负责的好领导。这句话从他口中说出，应该是在我潜意识里产生了一种奇异的反差感，因而印象深刻，即使当时的我并没有想清其中原因。我更愿意相信，那时的他说出这样的话，其实也是被其他人PUA而不自知。算起来，我和这位曾经的领导也已有十多年没见过面，不知他近况如何，希望他现在一切都好。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;若干年前，我当时所在一家公司的领导会时不时地对我们说：我们在工作中要有ownership，要尽力承担更多的责任。按理说，这是一句很平常的话，很多领导都喜欢说。但很奇怪，他的这句话让我一直印象深刻。直到最近这两天，偶然间我想明白了其中的原因。&lt;/p&gt;
&lt;p&gt;回到问题的本质，o</summary>
      
    
    
    
    
    <category term="record" scheme="https://gangmax.me/tags/record/"/>
    
    <category term="thought" scheme="https://gangmax.me/tags/thought/"/>
    
    <category term="chatgpt" scheme="https://gangmax.me/tags/chatgpt/"/>
    
  </entry>
  
  <entry>
    <title>Fix The &quot;pkg_resources is deprecated as an API&quot; Warning</title>
    <link href="https://gangmax.me/blog/2025/05/28/fix-the-pkgresources-is-deprecated-as-an-api-warning/"/>
    <id>https://gangmax.me/blog/2025/05/28/fix-the-pkgresources-is-deprecated-as-an-api-warning/</id>
    <published>2025-05-28T06:53:43.000Z</published>
    <updated>2026-04-17T07:25:55.195Z</updated>
    
    <content type="html"><![CDATA[<p>When I was running a tool compiled by PyInstaller with Python code, I got the following warning message:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">PyInstaller/loader/pyimod02_importers.py:450: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated <span class="keyword">for</span> removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools&lt;81.</span><br></pre></td></tr></table></figure><p>I googled this and got the solution <a href="https://github.com/yt-dlp/yt-dlp/issues/13311#issuecomment-2914322758">here</a>, which was to add the “–exclude-module pkg_resources” argument in the “pyinstaller” command to exclude the “pkg_resources” package from the build.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;When I was running a tool compiled by PyInstaller with Python code, I got the following warning message:&lt;/p&gt;
&lt;figure class=&quot;highlight bas</summary>
      
    
    
    
    
    <category term="python" scheme="https://gangmax.me/tags/python/"/>
    
    <category term="pyinstaller" scheme="https://gangmax.me/tags/pyinstaller/"/>
    
  </entry>
  
  <entry>
    <title>Setup Clipboard Folder Sharing in VirtualBox for Debian</title>
    <link href="https://gangmax.me/blog/2025/05/14/setup-clipboard-folder-sharing-in-virtualbox-for-debian/"/>
    <id>https://gangmax.me/blog/2025/05/14/setup-clipboard-folder-sharing-in-virtualbox-for-debian/</id>
    <published>2025-05-14T02:45:54.000Z</published>
    <updated>2026-04-17T07:25:55.195Z</updated>
    
    <content type="html"><![CDATA[<p>From <a href="https://forums.virtualbox.org/viewtopic.php?t=82867">here</a> and <a href="https://t-cr.jp/article/32e179db887d5839">here</a>.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Fix the &quot;Kernel headers not found for target kernel&quot; issue when running</span></span><br><span class="line"><span class="comment"># &quot;sudo sh ./VBoxLinuxAdditions-arm64.run&quot;.</span></span><br><span class="line">sudo apt-get install linux-headers-generic</span><br><span class="line"></span><br><span class="line"><span class="comment"># Install this to make &quot;folder/clipboard&quot; sharing work.</span></span><br><span class="line">sudo sh ./VBoxLinuxAdditions-arm64.run</span><br><span class="line"></span><br><span class="line"><span class="comment"># Make the current user have permission to access the shared folder.</span></span><br><span class="line">sudo usermod -a -G vboxsf auser</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;From &lt;a href=&quot;https://forums.virtualbox.org/viewtopic.php?t=82867&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://t-cr.jp/article/32e179db887d5839&quot;&gt;here&lt;/</summary>
      
    
    
    
    
    <category term="virtualbox" scheme="https://gangmax.me/tags/virtualbox/"/>
    
  </entry>
  
  <entry>
    <title>BAD PASSWORD: The password fails the dictionary check</title>
    <link href="https://gangmax.me/blog/2025/04/25/bad-password-the-password-fails-the-dictionary-check/"/>
    <id>https://gangmax.me/blog/2025/04/25/bad-password-the-password-fails-the-dictionary-check/</id>
    <published>2025-04-25T02:23:42.000Z</published>
    <updated>2026-04-17T07:25:55.195Z</updated>
    
    <content type="html"><![CDATA[<p>When I run “passwd” command in a Ubuntu “24.10” VM to change the password, it reports “BAD PASSWORD: The password fails the dictionary check - it is based on a dictionary word”. I didn’t encounter this issue in the earlier Ubuntu versions I used. How to make it work as before?</p><p>The solution below comes from “<a href="https://chatgpt.com/">ChatGPT</a>“.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Edit the configuration file which controls the behavior of &quot;passwd&quot;.</span></span><br><span class="line">sudo vi /etc/pam.d/common-password</span><br><span class="line"></span><br><span class="line"><span class="comment"># Update the following line:</span></span><br><span class="line">password requisite pam_pwquality.so retry=3</span><br><span class="line"></span><br><span class="line"><span class="comment"># to:</span></span><br><span class="line">password requisite pam_pwquality.so retry=3 minlen=6 dictcheck=0</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;When I run “passwd” command in a Ubuntu “24.10” VM to change the password, it reports “BAD PASSWORD: The password fails the dictionary ch</summary>
      
    
    
    
    
    <category term="ubuntu" scheme="https://gangmax.me/tags/ubuntu/"/>
    
    <category term="command" scheme="https://gangmax.me/tags/command/"/>
    
  </entry>
  
  <entry>
    <title>Record Type in Java</title>
    <link href="https://gangmax.me/blog/2025/03/28/record-type-in-java/"/>
    <id>https://gangmax.me/blog/2025/03/28/record-type-in-java/</id>
    <published>2025-03-28T04:20:14.000Z</published>
    <updated>2026-04-17T07:25:55.195Z</updated>
    
    <content type="html"><![CDATA[<p>From ChatGPT and <a href="https://docs.oracle.com/en/java/javase/17/language/records.html">this</a> post.</p><ol><li><p>In a record type, the appropriate accessors, constructor, equals, hashCode, and toString methods are created automatically.</p></li><li><p>Record type is used for immutable data. It means all record fields in it are final, and they can only be set within a constructor, either a “cononical constructor”(set all fields explicitly) or an “overloaded constructor”(set all fields with given field value(s) and the default value(s)). No-argument constructor is not allowed since it doesn’t make sence for immutable data.</p></li><li><p>Compact Constructor can be used to perform extra validation, transformation, or initialization logic.</p> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">record</span> <span class="title class_">Person</span><span class="params">(String name, <span class="type">int</span> age)</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// Canonical constructor is automatically provided</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// Compact constructor (custom constructor)</span></span><br><span class="line">    <span class="keyword">public</span> Person &#123;</span><br><span class="line">        <span class="keyword">if</span> (age &lt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Age cannot be negative&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>Record type cannot extend from other class(including another record type) or be extended by other subclass(including another record type), while it can implement interface, and can be a filed of another record type. You can take it as a special “final” class.</p></li><li><p>You cannot declare instance variables (non-static fields) or instance initializers in a record class.</p></li></ol><p>Another <a href="https://openjdk.org/projects/amber/guides/lvti-style-guide">post</a> is also a good one for Java local variable type inference.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;From ChatGPT and &lt;a href=&quot;https://docs.oracle.com/en/java/javase/17/language/records.html&quot;&gt;this&lt;/a&gt; post.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;In a record typ</summary>
      
    
    
    
    
    <category term="java" scheme="https://gangmax.me/tags/java/"/>
    
  </entry>
  
  <entry>
    <title>A Java Code Sample of High Order</title>
    <link href="https://gangmax.me/blog/2025/03/24/a-java-code-sample-of-high-order/"/>
    <id>https://gangmax.me/blog/2025/03/24/a-java-code-sample-of-high-order/</id>
    <published>2025-03-24T18:07:12.000Z</published>
    <updated>2026-04-17T07:25:55.195Z</updated>
    
    <content type="html"><![CDATA[<p>The code below shows how to convert a code repetition into a simplified abstraction with high order function implementation. At the first glance, this might be unnecessary. However as the code lines increase, such abstractions will make the code neat and short.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.apache.commons.lang3.tuple.Triple;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.function.BiConsumer;</span><br><span class="line"><span class="keyword">import</span> java.util.function.Function;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Actions</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">get</span><span class="params">(String path, Function&lt;String, String&gt; handler)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;get path = &quot;</span> + path);</span><br><span class="line">        System.out.println(<span class="string">&quot;get handler result = &quot;</span> + handler.apply(path));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">post</span><span class="params">(String path, Function&lt;String, String&gt; handler)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;post path = &quot;</span> + path);</span><br><span class="line">        System.out.println(<span class="string">&quot;post handler result = &quot;</span> + handler.apply(path));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// The original code.</span></span><br><span class="line">        runPlainOld();</span><br><span class="line">        <span class="comment">// The updated code.</span></span><br><span class="line">        List&lt;Triple&lt;BiConsumer&lt;String, Function&lt;String, String&gt;&gt;, String, Function&lt;String, String&gt;&gt;&gt;  triples = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        triples.add(Triple.of(Actions::get, <span class="string">&quot;/running&quot;</span>, name -&gt; name));</span><br><span class="line">        triples.add(Triple.of(Actions::get, <span class="string">&quot;/:taskId&quot;</span>, jobId -&gt; <span class="string">&quot;taskId=&quot;</span> + jobId));</span><br><span class="line">        triples.add(Triple.of(Actions::post, <span class="string">&quot;/sayhello&quot;</span>, name -&gt; <span class="string">&quot;Hello, &quot;</span> + name));</span><br><span class="line">        runHighOrder(triples);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">runPlainOld</span><span class="params">()</span> &#123;</span><br><span class="line">        get(<span class="string">&quot;/running&quot;</span>, name -&gt; name);</span><br><span class="line">        get(<span class="string">&quot;/:taskId&quot;</span>, jobId -&gt; <span class="string">&quot;taskId=&quot;</span> + jobId);</span><br><span class="line">        post(<span class="string">&quot;/sayhello&quot;</span>, name -&gt; <span class="string">&quot;Hello, &quot;</span> + name);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">runHighOrder</span><span class="params">(List&lt;Triple&lt;BiConsumer&lt;String, Function&lt;String, String&gt;&gt;, String, Function&lt;String, String&gt;&gt;&gt;  triples)</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (Triple&lt;BiConsumer&lt;String, Function&lt;String, String&gt;&gt;, String, Function&lt;String, String&gt;&gt; triple : triples) &#123;</span><br><span class="line">            triple.getLeft().accept(triple.getMiddle(), triple.getRight());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;The code below shows how to convert a code repetition into a simplified abstraction with high order function implementation. At the first</summary>
      
    
    
    
    
    <category term="java" scheme="https://gangmax.me/tags/java/"/>
    
  </entry>
  
  <entry>
    <title>How to Calculate The Size of Raw Uncompressed Audio File</title>
    <link href="https://gangmax.me/blog/2025/03/20/how-to-calculate-the-size-of-raw-uncompressed-audio-file/"/>
    <id>https://gangmax.me/blog/2025/03/20/how-to-calculate-the-size-of-raw-uncompressed-audio-file/</id>
    <published>2025-03-20T17:41:24.000Z</published>
    <updated>2026-04-17T07:25:55.195Z</updated>
    
    <content type="html"><![CDATA[<p>Here is the formula which is straightforward.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">File size = sampling rate(Hz) * bit depth(Byte) * 2(stereo track) * time(Second)</span><br></pre></td></tr></table></figure><p>And the samples how to use it.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># A 1-minute stereo track at 44.1 kHz and 16-bit depth can easily be around 10 MB.</span></span><br><span class="line">File size = 44100(sampling rate) * 2(bit depth) * 2(stereo track) * 60(time) = 10,584,000(bytes)</span><br><span class="line"></span><br><span class="line"><span class="comment"># A 76-minute stereo track at 44.1 kHz and 16-bit depth(Standard audio CD):</span></span><br><span class="line">File size = 44100(sampling rate) * 2(bit depth) * 2(stereo track) * 60 * 76(time) = 804,384,000(bytes)</span><br><span class="line"></span><br><span class="line"><span class="comment"># A 5-minute stereo track at 196 kHz and 32-bit depth:</span></span><br><span class="line">File size = 192000(sampling rate) * 4(bit depth) * 2(stereo track) * 300(time) = 460,800,000(bytes)</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Here is the formula which is straightforward.&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;</summary>
      
    
    
    
    
    <category term="audio" scheme="https://gangmax.me/tags/audio/"/>
    
  </entry>
  
  <entry>
    <title>Generate Random Data in Python</title>
    <link href="https://gangmax.me/blog/2025/03/05/generate-random-data-in-python/"/>
    <id>https://gangmax.me/blog/2025/03/05/generate-random-data-in-python/</id>
    <published>2025-03-05T17:34:57.000Z</published>
    <updated>2026-04-17T07:25:55.194Z</updated>
    
    <content type="html"><![CDATA[<p>Sometimes I want to generate some random data, such as strings by a given regular expression. Here are some Python libraries which can be used.</p><h3 id="rstr"><a href="#rstr" class="headerlink" title="rstr"></a><a href="https://github.com/leapfrogonline/rstr">rstr</a></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&gt;&gt;&gt; import rstr</span><br><span class="line">&gt;&gt;&gt; [rstr.xeger(r<span class="string">&#x27;^[a-z0-9]&#123;6,6&#125;$&#x27;</span>) <span class="keyword">for</span> i <span class="keyword">in</span> range(5)]</span><br><span class="line">[<span class="string">&#x27;qazxpj&#x27;</span>, <span class="string">&#x27;spt53j&#x27;</span>, <span class="string">&#x27;rvfkmu&#x27;</span>, <span class="string">&#x27;juwerj&#x27;</span>, <span class="string">&#x27;erlrz7&#x27;</span>]</span><br></pre></td></tr></table></figure><h3 id="exrex"><a href="#exrex" class="headerlink" title="exrex"></a><a href="https://github.com/asciimoo/exrex">exrex</a></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&gt;&gt;&gt; import exrex</span><br><span class="line">&gt;&gt;&gt; [exrex.getone(<span class="string">&#x27;[a-z0-9]&#123;6,7&#125;&#x27;</span>) <span class="keyword">for</span> i <span class="keyword">in</span> range(5)]</span><br><span class="line">[<span class="string">&#x27;ac02qf4&#x27;</span>, <span class="string">&#x27;h7ik7xk&#x27;</span>, <span class="string">&#x27;uk3hvd6&#x27;</span>, <span class="string">&#x27;i6soqy&#x27;</span>, <span class="string">&#x27;mtwslb&#x27;</span>]</span><br></pre></td></tr></table></figure><h3 id="faker"><a href="#faker" class="headerlink" title="faker"></a><a href="https://faker.readthedocs.io/en/master/">faker</a></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&gt;&gt;&gt; from faker import Faker</span><br><span class="line">&gt;&gt;&gt; fake = Faker(locale=[<span class="string">&#x27;zh_CN&#x27;</span>, <span class="string">&#x27;jp_JP&#x27;</span>, <span class="string">&#x27;en_NZ&#x27;</span>])</span><br><span class="line">&gt;&gt;&gt; [fake.name() <span class="keyword">for</span> i <span class="keyword">in</span> range(7)]</span><br><span class="line">[<span class="string">&#x27;高橋 翔太&#x27;</span>, <span class="string">&#x27;叶欢&#x27;</span>, <span class="string">&#x27;鈴木 花子&#x27;</span>, <span class="string">&#x27;郑旭&#x27;</span>, <span class="string">&#x27;Vicki Morton-Smith&#x27;</span>, <span class="string">&#x27;Shane Phillips&#x27;</span>, <span class="string">&#x27;木村 和也&#x27;</span>]</span><br><span class="line">&gt;&gt;&gt; [fake.email() <span class="keyword">for</span> i <span class="keyword">in</span> range(7)]</span><br><span class="line">[<span class="string">&#x27;momoko27@example.com&#x27;</span>, <span class="string">&#x27;gaoming@example.com&#x27;</span>, <span class="string">&#x27;elin@example.com&#x27;</span>, <span class="string">&#x27;hidekiaoki@example.com&#x27;</span>, <span class="string">&#x27;james47@example.com&#x27;</span>, <span class="string">&#x27;francissean@example.net&#x27;</span>, <span class="string">&#x27;minoru11@example.com&#x27;</span>]</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Sometimes I want to generate some random data, such as strings by a given regular expression. Here are some Python libraries which can be</summary>
      
    
    
    
    
    <category term="python" scheme="https://gangmax.me/tags/python/"/>
    
  </entry>
  
  <entry>
    <title>Vert.x Error When Running within Fat Jar</title>
    <link href="https://gangmax.me/blog/2025/02/27/vertx-error-when-running-within-fat-jar/"/>
    <id>https://gangmax.me/blog/2025/02/27/vertx-error-when-running-within-fat-jar/</id>
    <published>2025-02-27T18:20:15.000Z</published>
    <updated>2026-04-17T07:25:55.194Z</updated>
    
    <content type="html"><![CDATA[<p>I have a web application project written with “vert.x”. When I run it in IntelliJ IDEA it works fine, however when I run it as a fat jar it reports the following error when starting and then stops:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&gt; java -jar revas-1.0.0-all.jar</span><br><span class="line"><span class="built_in">date</span>=2025-02-27T10:31:55,361,level=INFO,thread=main,logger=revas.starter.MainApp: Starting <span class="string">&#x27;maps-china-request-validator&#x27;</span> service...</span><br><span class="line"><span class="built_in">date</span>=2025-02-27T10:31:55,607,level=INFO,thread=main,logger=revas.starter.MainApp: options.getStores().size(): 0</span><br><span class="line">Exception <span class="keyword">in</span> thread <span class="string">&quot;main&quot;</span> java.lang.IllegalArgumentException: unknown configuration store implementation: json (known implementations are: [vault])</span><br><span class="line">    at io.vertx.config.impl.ConfigRetrieverImpl.&lt;init&gt;(ConfigRetrieverImpl.java:111)</span><br><span class="line">    at io.vertx.config.ConfigRetriever.create(ConfigRetriever.java:53)</span><br><span class="line">    at revas.starter.MainApp.main(MainApp.java:60)</span><br></pre></td></tr></table></figure><p>I googled “unknown configuration store implementation: json (known implementations are: [vault]” and find the following posts:</p><ol><li><p><a href="https://stackoverflow.com/questions/59785238/vertx-config-with-spring-config-server-unknown-configuration-store-implementati">Explanation</a></p></li><li><p><a href="https://github.com/vert-x3/vertx-config/issues/75#issuecomment-765229154">Solution</a></p></li></ol><p>The root cause is that, when packaging a fat jar, by default it won’t merge the service files under the “META-INF&#x2F;services&#x2F;“ path in each jar file. So when there’re multiple jar files with such service files, some content is missing. To make the fat jar creation process do the merge, add the following content into the “build.gradle” file.</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">shadowJar&#123;</span><br><span class="line">    mergeServiceFiles()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>After that, the created fat jar works fine.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;I have a web application project written with “vert.x”. When I run it in IntelliJ IDEA it works fine, however when I run it as a fat jar </summary>
      
    
    
    
    
    <category term="vertx" scheme="https://gangmax.me/tags/vertx/"/>
    
  </entry>
  
  <entry>
    <title>Test HTTP Proxy with Linux Command</title>
    <link href="https://gangmax.me/blog/2025/02/20/test-http-proxy-with-linux-command/"/>
    <id>https://gangmax.me/blog/2025/02/20/test-http-proxy-with-linux-command/</id>
    <published>2025-02-20T19:11:09.000Z</published>
    <updated>2026-04-17T07:25:55.194Z</updated>
    
    <content type="html"><![CDATA[<p>From <a href="https://help.limeproxies.com/en/articles/5275112-how-to-test-proxies-in-the-linux-command-line-interface">here</a>.</p><p>To test a given proxy works or not, you can use the following command:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Request the headers only. HTTP status code 200 should be returned.</span></span><br><span class="line">curl -I www.bing.com</span><br><span class="line"><span class="comment"># Request the headers by using the given &quot;proxy host/port&quot;.</span></span><br><span class="line"><span class="comment"># If the proxy works, HTTP status code 200 will be returned.</span></span><br><span class="line"><span class="comment"># Otherwise you should get an error such as below:</span></span><br><span class="line"><span class="comment">#   curl: (52) Empty reply from server</span></span><br><span class="line">curl -x http://proxy.server.domain.name:8000 -I www.bing.com</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;From &lt;a href=&quot;https://help.limeproxies.com/en/articles/5275112-how-to-test-proxies-in-the-linux-command-line-interface&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;</summary>
      
    
    
    
    
    <category term="linux" scheme="https://gangmax.me/tags/linux/"/>
    
    <category term="proxy" scheme="https://gangmax.me/tags/proxy/"/>
    
  </entry>
  
  <entry>
    <title>Run LLM Locally</title>
    <link href="https://gangmax.me/blog/2025/02/13/run-llm-locally/"/>
    <id>https://gangmax.me/blog/2025/02/13/run-llm-locally/</id>
    <published>2025-02-13T18:42:31.000Z</published>
    <updated>2026-04-17T07:25:55.194Z</updated>
    
    <content type="html"><![CDATA[<p>AFAIK, currently there’re several ways to run LLM(large language model) locally.</p><h3 id="Ollama"><a href="#Ollama" class="headerlink" title="Ollama"></a>Ollama</h3><p>Go to “<a href="https://ollama.com/">Ollama</a>“ website to download the “ollama” application for your OS(Linux&#x2F;macOS&#x2F;Windows). Install it. Then you need to select a model to run with Ollama. Go to <a href="https://ollama.com/models">this</a> page to select. For example, I select “deepseek-r1:32b” from <a href="https://ollama.com/library/deepseek-r1">here</a>.</p><p>Run the following command to start.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ollama run deepseek-r1:32b</span><br></pre></td></tr></table></figure><p>The first time you run a given model, it will be downloaded and then start.</p><p>After “ollama” is running locally, you can install the “<a href="https://github.com/n4ze3m/page-assist">Page Assist</a>“ browser extension, to chat in browser instead of in terminal.</p><h3 id="llamafile-standalone-with-given-model-file"><a href="#llamafile-standalone-with-given-model-file" class="headerlink" title="llamafile(standalone with given model file)"></a>llamafile(standalone with given model file)</h3><p>From <a href="https://linuxtoy.org/archives/local-code-assistant-with-llamafile-and-continue.html">here</a>.</p><p>In this mode, you need to have the “llamafile” application and a model file.</p><ol><li><p>Download “llamafile” from “<a href="https://github.com/Mozilla-Ocho/llamafile/releases">here</a>“, such as “llamafile-0.9.0”. Extract the zip file if required and “chmod +x” to make it executable.</p></li><li><p>Go to <a href="https://www.modelscope.cn/models">ModelScope</a> and select a “GGUF” format module file, such as “<a href="https://www.modelscope.cn/models/Qwen/Qwen2.5-Coder-7B-Instruct-GGUF/">Qwen2.5-Coder-7B-Instruct-GGUF</a>“ and download.</p></li></ol><p>Run.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Before running, you can run &quot;./llamafile-0.9.0 --help&quot; to get the knowledge</span></span><br><span class="line"><span class="comment"># how to set the &quot;-ngl&quot; or &quot;--gpu&quot; arguments, to leverage the CPU/GPU resources</span></span><br><span class="line"><span class="comment"># of your computer better.</span></span><br><span class="line">./llamafile-0.9.0 -m qwen2.5-coder-7b-instruct-q5_k_m.gguf -ngl 999 --log-disable</span><br></pre></td></tr></table></figure><h3 id="llamafile-all-in-one"><a href="#llamafile-all-in-one" class="headerlink" title="llamafile(all-in-one)"></a>llamafile(all-in-one)</h3><p>From <a href="https://github.com/Mozilla-Ocho/llamafile">here</a>.</p><p>In this mode, all you need is to download one file such as “<a href="https://huggingface.co/Mozilla/llava-v1.5-7b-llamafile/resolve/main/llava-v1.5-7b-q4.llamafile?download=true">llava-v1.5-7b-q4.llamafile</a>“, make it executable(“chmod +x”) and run:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./llava-v1.5-7b-q4.llamafile</span><br></pre></td></tr></table></figure><p>After that your browser should be opened automatically and display a chat interface. You can adjust the settings and start chatting.</p><p>An interesting part you may not notice above is that, one “llamafile” binary file can be used in almost all known operating systems like “Linux”, “BSD”, “macOS” and “Windows”, which is accomplished by combining “<a href="https://github.com/ggerganov/llama.cpp">llama.cpp</a>“ and “<a href="https://justine.lol/cosmopolitan/index.html">Cosmopolitan Libc</a>“.</p><h3 id="koboldcpp"><a href="#koboldcpp" class="headerlink" title="koboldcpp"></a>koboldcpp</h3><p>From <a href="https://linuxtoy.org/archives/local-research-assistant-with-koboldcpp-pot-and-gpt4all.html">here</a>.</p><p>Go to “<a href="https://github.com/LostRuins/koboldcpp">koboldcpp</a>“ official website, download the binary file, such as “koboldcpp-mac-arm64” for macOS. Make it executable(“chmod +x”) and run:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./koboldcpp-mac-arm64</span><br></pre></td></tr></table></figure><p>This command opens a window in which you need to set the “GGUF Text Model” field with a “gguf” file(model file). You can download such “gguf” files from websites like “<a href="https://www.modelscope.cn/">modelscope</a>“ or “<a href="https://huggingface.co/">huggingface</a>“.</p><p>Note that, after you launch the model, you can either chat in the opened browser window, or use the APIs hosted at “<a href="http://localhost:5001/v1/">http://localhost:5001/v1/</a>“. More details about how to use the APIs can be found in the official document <a href="https://lite.koboldai.net/koboldcpp_api">here</a>. Here is a sample.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">curl http://localhost:5001/v1/chat/completions \</span><br><span class="line">  -H <span class="string">&quot;Content-Type: application/json&quot;</span> \</span><br><span class="line">  -d <span class="string">&#x27;&#123;&quot;messages&quot;:[&#123;&quot;role&quot;:&quot;user&quot;,&quot;content&quot;:&quot;How to install Rust on Ubuntu?&quot;&#125;]&#125;&#x27;</span></span><br></pre></td></tr></table></figure><p>It will take a while to get the response, just be patient.</p><h3 id="Resources"><a href="#Resources" class="headerlink" title="Resources"></a>Resources</h3><ol><li><p><a href="https://huggingface.co/">Hugging Face</a>: The platform where the machine learning community collaborates on models, datasets, and applications.</p></li><li><p><a href="https://www.modelscope.cn/">ModelScope</a>: 共享&#x2F;共创&#x2F;共进，构建持续创新的 AI 开源生态。And the team ever create a Python library “<a href="https://pypi.org/project/modelscope/">modelscope</a>“. The library seeks to bring together most advanced machine learning models from the AI community, and streamlines the process of leveraging AI models in real-world applications.</p></li><li><p><a href="https://trendshift.io/">TrendShift</a>: A dashboard shows the trending GitHub repositories and developers.</p></li><li><p><a href="https://github.com/codecrafters-io/build-your-own-x">build-your-own-x</a>: This Git repository is a compilation of well-written, step-by-step guides for re-creating our favorite technologies from scratch. For example, “<a href="https://markuseliasson.se/article/bittorrent-in-python/">Python: A BitTorrent client in Python 3.5</a>“.</p></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;AFAIK, currently there’re several ways to run LLM(large language model) locally.&lt;/p&gt;
&lt;h3 id=&quot;Ollama&quot;&gt;&lt;a href=&quot;#Ollama&quot; class=&quot;headerlink&quot;</summary>
      
    
    
    
    
    <category term="ai" scheme="https://gangmax.me/tags/ai/"/>
    
    <category term="llm" scheme="https://gangmax.me/tags/llm/"/>
    
  </entry>
  
  <entry>
    <title>pipx</title>
    <link href="https://gangmax.me/blog/2025/02/12/pipx/"/>
    <id>https://gangmax.me/blog/2025/02/12/pipx/</id>
    <published>2025-02-12T18:27:16.000Z</published>
    <updated>2026-04-17T07:25:55.194Z</updated>
    
    <content type="html"><![CDATA[<p>“<a href="https://github.com/pypa/pipx">pipx</a>“ is a tool for managing Python applications in isolated environments. For me, It’s used to install CLI tools written in Python as normal executable binary files, within isolated virtal Python environments which you don’t have to know about. For example, you can use “pipx” to install “<a href="https://python-poetry.org/">poetry</a>“. After that you can use “poetry” as a normal CLI command.</p><p>Use the following instructions to install “pipx”.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># On Mac:</span></span><br><span class="line">brew install pipx</span><br><span class="line">pipx ensurepath</span><br><span class="line"></span><br><span class="line"><span class="comment"># On Ubuntu:</span></span><br><span class="line">sudo apt install pipx</span><br><span class="line">pipx ensurepath</span><br></pre></td></tr></table></figure><p>Use “pipx”.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Install package.</span></span><br><span class="line">pipx install poetry</span><br><span class="line"></span><br><span class="line"><span class="comment"># List installed packages.</span></span><br><span class="line">pipx list</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;“&lt;a href=&quot;https://github.com/pypa/pipx&quot;&gt;pipx&lt;/a&gt;“ is a tool for managing Python applications in isolated environments. For me, It’s used </summary>
      
    
    
    
    
    <category term="python" scheme="https://gangmax.me/tags/python/"/>
    
    <category term="pipx" scheme="https://gangmax.me/tags/pipx/"/>
    
  </entry>
  
  <entry>
    <title>Decode JWT Token</title>
    <link href="https://gangmax.me/blog/2025/01/15/decode-jwt-token/"/>
    <id>https://gangmax.me/blog/2025/01/15/decode-jwt-token/</id>
    <published>2025-01-15T15:15:08.000Z</published>
    <updated>2026-04-17T07:25:55.194Z</updated>
    
    <content type="html"><![CDATA[<p>From <a href="https://www.baeldung.com/java-jwt-token-decode">here</a>, <a href="https://stackoverflow.com/a/29419920/3115617">here</a> and <a href="https://jwt.io/introduction">here</a>.</p><p>Today I realized that the JWT token is completely plain text which can be decoded very easily. You can use some online tools(such as <a href="https://jwt.io/#encoded-jwt">this</a> and <a href="https://www.jstoolset.com/jwt">this</a>), or the following Python script.</p><figure class="highlight bash"><figcaption><span>dejwt.py</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">from <span class="built_in">base64</span> import urlsafe_b64decode</span><br><span class="line">import json</span><br><span class="line">import sys</span><br><span class="line"></span><br><span class="line">def decode_jwt(token: str) -&gt; dict:</span><br><span class="line">    chunks = token.split(<span class="string">&#x27;.&#x27;</span>)</span><br><span class="line">    header = str(urlsafe_b64decode(chunks[0] + <span class="string">&#x27;==&#x27;</span>), encoding=<span class="string">&#x27;utf-8&#x27;</span>)</span><br><span class="line">    payload = str(urlsafe_b64decode(chunks[1] + <span class="string">&#x27;==&#x27;</span>), encoding=<span class="string">&#x27;utf-8&#x27;</span>)</span><br><span class="line">    signature = chunks[2]</span><br><span class="line">    result = &#123;</span><br><span class="line">        <span class="string">&#x27;header&#x27;</span>: json.loads(header),</span><br><span class="line">        <span class="string">&#x27;payload&#x27;</span>: json.loads(payload),</span><br><span class="line">        <span class="string">&#x27;signature&#x27;</span>: signature</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">return</span> result</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    <span class="keyword">if</span> len(sys.argv) &lt; 2:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;Usage: python dejwt.py &lt;jwt_token&gt;&#x27;</span>)</span><br><span class="line">        <span class="built_in">exit</span>(0)</span><br><span class="line">    <span class="built_in">print</span>(json.dumps(decode_jwt(sys.argv[1]), indent=4))</span><br></pre></td></tr></table></figure><p>And the running sample:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">~&gt; python dejwt.py <span class="string">&#x27;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c&#x27;</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;header&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;alg&quot;</span>: <span class="string">&quot;HS256&quot;</span>,</span><br><span class="line">        <span class="string">&quot;typ&quot;</span>: <span class="string">&quot;JWT&quot;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&quot;payload&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;sub&quot;</span>: <span class="string">&quot;1234567890&quot;</span>,</span><br><span class="line">        <span class="string">&quot;name&quot;</span>: <span class="string">&quot;John Doe&quot;</span>,</span><br><span class="line">        <span class="string">&quot;iat&quot;</span>: 1516239022</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&quot;signature&quot;</span>: <span class="string">&quot;SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The structure of JWT token is well explained by the script above. More details can be found in the official document <a href="https://jwt.io/introduction">here</a> and <a href="https://datatracker.ietf.org/doc/html/rfc7519">here</a>.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;From &lt;a href=&quot;https://www.baeldung.com/java-jwt-token-decode&quot;&gt;here&lt;/a&gt;, &lt;a href=&quot;https://stackoverflow.com/a/29419920/3115617&quot;&gt;here&lt;/a&gt; a</summary>
      
    
    
    
    
    <category term="jwt" scheme="https://gangmax.me/tags/jwt/"/>
    
  </entry>
  
  <entry>
    <title>&quot;Chinese New Year&quot; Is Not &quot;Lunar New Year&quot;</title>
    <link href="https://gangmax.me/blog/2024/12/18/lunar-new-year/"/>
    <id>https://gangmax.me/blog/2024/12/18/lunar-new-year/</id>
    <published>2024-12-18T12:29:29.000Z</published>
    <updated>2026-04-17T07:25:55.194Z</updated>
    
    <content type="html"><![CDATA[<p>Some US people like to use the term “Lunar New Year” to express the concept of “Chinese New Year” for better political correctness. However, I guess they don’t know the fact that the traditional Chinese calendar is not a “lunar” calendar, but a “lunisolar” calendar. So actually the Chinese new year is not “lunar” new year, instead, if they don’t want to use “Chinese” here, “Lunisolar New Year” is a better alternative. The concept “Lunar New Year” seems a better fit to the “Islamic New Year(or Hijri New Year)” because it’s purely lunar based.</p><p>If you want to get more details about this topic, the following ChatGPT session provides them.</p><p><img src="/images/posts/20241218_lunar_new_year.jpg" alt="ChatGPT"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Some US people like to use the term “Lunar New Year” to express the concept of “Chinese New Year” for better political correctness. Howev</summary>
      
    
    
    
    
    <category term="calendar" scheme="https://gangmax.me/tags/calendar/"/>
    
    <category term="chatgpt" scheme="https://gangmax.me/tags/chatgpt/"/>
    
  </entry>
  
  <entry>
    <title>Update Git Configuration</title>
    <link href="https://gangmax.me/blog/2024/12/02/update-git-configuration/"/>
    <id>https://gangmax.me/blog/2024/12/02/update-git-configuration/</id>
    <published>2024-12-02T12:37:40.000Z</published>
    <updated>2026-04-17T07:25:55.194Z</updated>
    
    <content type="html"><![CDATA[<p>As I knew, I could add git config by the following commands:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Add &quot;local&quot; config.</span></span><br><span class="line">git config user.email <span class="string">&quot;your_email@abc.example&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Add &quot;global&quot; config.</span></span><br><span class="line">git config --global user.email <span class="string">&quot;your_email@abc.example&quot;</span></span><br></pre></td></tr></table></figure><p>What about removing a configuration item? A simple answer is using the following commands(from <a href="https://graphite.dev/guides/git-unset-config">here</a>):</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Update(include removing) &quot;local&quot; config.</span></span><br><span class="line">git config --edit</span><br><span class="line"></span><br><span class="line"><span class="comment"># Update &quot;global&quot; config.</span></span><br><span class="line">git config --global --edit</span><br></pre></td></tr></table></figure><p>The two commands start an editor app(such as “vi” or “Sublime Text”, depends on your local setting) to open the configuration files:</p><ol><li><p>Local: “.git&#x2F;config”</p></li><li><p>Global: “~&#x2F;.gitconfig”</p></li></ol><p>In another word, you can edit the configuration files directly to “add&#x2F;update&#x2F;remove” the configuration items.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;As I knew, I could add git config by the following commands:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span </summary>
      
    
    
    
    
    <category term="git" scheme="https://gangmax.me/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>Use External log4j2 Configuration File</title>
    <link href="https://gangmax.me/blog/2024/11/15/use-external-log4j2-configuration-file/"/>
    <id>https://gangmax.me/blog/2024/11/15/use-external-log4j2-configuration-file/</id>
    <published>2024-11-15T18:13:34.000Z</published>
    <updated>2026-04-17T07:25:55.193Z</updated>
    
    <content type="html"><![CDATA[<p>When running a Java application, if I want to use an external “log4j2” configuration file instead of the default one inside the jar file, how can I do?</p><p>The answer below comes from the posts <a href="https://logging.apache.org/log4j/2.x/manual/configuration.html#automatic-configuration">here</a> and <a href="https://stackoverflow.com/a/55160891/3115617">here</a>.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -Dlog4j2.configurationFile=file:/home/auser/code/sample-project/src/test/resources/log4j2-test.xml -jar sample-project-1.0.0-all.jar</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;When running a Java application, if I want to use an external “log4j2” configuration file instead of the default one inside the jar file,</summary>
      
    
    
    
    
    <category term="java" scheme="https://gangmax.me/tags/java/"/>
    
    <category term="log4j" scheme="https://gangmax.me/tags/log4j/"/>
    
  </entry>
  
  <entry>
    <title>WARNING: sun.reflect.Reflection.getCallerClass is not supported</title>
    <link href="https://gangmax.me/blog/2024/11/15/warning-sunreflectreflectiongetcallerclass-is-not-supported/"/>
    <id>https://gangmax.me/blog/2024/11/15/warning-sunreflectreflectiongetcallerclass-is-not-supported/</id>
    <published>2024-11-15T10:59:55.000Z</published>
    <updated>2026-04-17T07:25:55.193Z</updated>
    
    <content type="html"><![CDATA[<p>Today I find when starting a Java application with the “java -jar” command, it reports the following warning:</p><blockquote><p>WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance</p></blockquote><p>Googling it and this <a href="https://github.com/aws/aws-lambda-java-libs/issues/204#issuecomment-1577693473">post</a> gives the answer: it’s introduced by the “log4j2” dependency. The original issue can be found <a href="https://issues.apache.org/jira/browse/LOG4J2-2537">here</a>. The fix is:</p><blockquote><p>…, you may need to have Multi-Release set to true in the jar manifest.</p></blockquote><p>So I need to know how to do such configuration in the “build.gradle” of the Java project which creates the jar file.</p><p>The final solution comes from this article “<a href="https://blog.gradle.org/mrjars">Multi-release JARs - Good or bad idea?</a>“, by adding the following content in the “build.gradle” file:</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">jar &#123;</span><br><span class="line">  manifest.attributes(</span><br><span class="line">     <span class="string">&#x27;Multi-Release&#x27;</span>: <span class="string">&#x27;true&#x27;</span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The “<a href="https://openjdk.org/jeps/238">Multi-release JARs</a>“ is a feature of JDK 9. The article “<a href="https://blog.gradle.org/mrjars">Multi-release JARs - Good or bad idea?</a>“ not only gives the steps how to create such jar file in Gradle, but also describes why this feature is not a good idea from his perspective.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Today I find when starting a Java application with the “java -jar” command, it reports the following warning:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;WARNING</summary>
      
    
    
    
    
    <category term="java" scheme="https://gangmax.me/tags/java/"/>
    
    <category term="gradle" scheme="https://gangmax.me/tags/gradle/"/>
    
  </entry>
  
  <entry>
    <title>Git: Change Default Branch from &quot;master&quot; to &quot;main&quot;</title>
    <link href="https://gangmax.me/blog/2024/11/06/git-change-default-branch-from-master-to-main/"/>
    <id>https://gangmax.me/blog/2024/11/06/git-change-default-branch-from-master-to-main/</id>
    <published>2024-11-06T18:40:57.000Z</published>
    <updated>2026-04-17T07:25:55.193Z</updated>
    
    <content type="html"><![CDATA[<p>First, you need to create the “main” branch as normal branch, and push it to remote.</p><p>In GitHub(the remote side), go the “Settings” of the repository, set the “main” branch as “Default branch”. Then open “View all branches” page of the repo, remove the “master” branch.</p><p>In local side, you need to following the instructions below(from <a href="https://stackoverflow.com/a/48100062/3115617">here</a>), to avoid the “Your configuration specifies to merge with the <branch name> from the remote, but no such ref was fetched” error when running “git pull”.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git checkout main</span><br><span class="line">git branch --unset-upstream</span><br><span class="line">git branch --set-upstream-to=origin/main</span><br></pre></td></tr></table></figure><p>Now you can run “git pull” without issue.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;First, you need to create the “main” branch as normal branch, and push it to remote.&lt;/p&gt;
&lt;p&gt;In GitHub(the remote side), go the “Settings”</summary>
      
    
    
    
    
    <category term="git" scheme="https://gangmax.me/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>Java Type Inference</title>
    <link href="https://gangmax.me/blog/2024/11/05/java-type-inference/"/>
    <id>https://gangmax.me/blog/2024/11/05/java-type-inference/</id>
    <published>2024-11-05T15:15:14.000Z</published>
    <updated>2026-04-17T07:25:55.193Z</updated>
    
    <content type="html"><![CDATA[<p>From <a href="https://dev.java/learn/generics/type-inference/">here</a>.</p><p>Please read the following Java code, which can be compiled and executed without any issue(with JDK 7+).</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.junit.jupiter.api.Test;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TypeParameterTest</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> &lt;T, X&gt; T <span class="title function_">read</span><span class="params">(X test)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">a</span> <span class="operator">=</span> TypeParameterTest.read(<span class="string">&quot;&quot;</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">b</span> <span class="operator">=</span> TypeParameterTest.read(<span class="number">123</span>);</span><br><span class="line">        <span class="type">Integer</span> <span class="variable">c</span> <span class="operator">=</span> TypeParameterTest.read(<span class="string">&quot;&quot;</span>);</span><br><span class="line">        <span class="type">Integer</span> <span class="variable">d</span> <span class="operator">=</span> TypeParameterTest.read(<span class="number">123</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Notes</strong></p><ol><li><p>The “read” method has 2 type parameters, one for argument and another for return value.</p></li><li><p>In the “test” method, 4 invocations of the “read” method have different argument types and return value types. The magic part is that, all of them can be compiled successfully. It means the “read” method can return different types in different circumstances, depending on where it’s invoked. This happens to the method argument as well.</p></li><li><p>The reason is that, the Java compiler has an ability called “type inference” which is used “<em>to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that make the invocation applicable. The inference algorithm determines the types of the arguments and, if available, the type that the result is being assigned, or returned. Finally, the inference algorithm tries to find the most specific type that works with all of the arguments</em>“(from <a href="https://dev.java/learn/generics/type-inference/">here</a>).</p></li></ol><p>I noticed this when I was reading how to use the “<a href="https://github.com/json-path/JsonPath">JsonPath</a>“ library <a href="https://github.com/json-path/JsonPath/blob/45333e0a310af70ad48d34d306da30af1e8e6314/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java#L549-L551">here</a>, and was astonished when I found the “read” method could return different types in different situations.</p><p>The full “<a href="https://dev.java/learn/generics/">Generics</a>“ tutorial is also worth reading.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;From &lt;a href=&quot;https://dev.java/learn/generics/type-inference/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Please read the following Java code, which can be compile</summary>
      
    
    
    
    
    <category term="java" scheme="https://gangmax.me/tags/java/"/>
    
  </entry>
  
  <entry>
    <title>Decode &quot;Escaped Octal Value&quot; in Proto Text</title>
    <link href="https://gangmax.me/blog/2024/10/23/decode-escaped-octal-value-in-proto-text/"/>
    <id>https://gangmax.me/blog/2024/10/23/decode-escaped-octal-value-in-proto-text/</id>
    <published>2024-10-23T12:58:01.000Z</published>
    <updated>2026-04-17T07:25:55.193Z</updated>
    
    <content type="html"><![CDATA[<p>In <a href="https://protobuf.dev/">protobuf</a> text, a string can be shown in the following “escaped octal value” format:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">some_string: &quot;\350\277\231\346\230\257\344\270\200\344\270\252\346\265\213\350\257\225&quot;</span><br></pre></td></tr></table></figure><p>Here is how to make the content readable.</p><p>From <a href="https://stackoverflow.com/a/23173530/3115617">here</a> and <a href="https://www.w3schools.com/python/gloss_python_escape_characters.asp">here</a>.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Convert an &quot;escaped octal value&quot; string to a text string:</span></span><br><span class="line"><span class="string">b&#x27;\345\255\246\346\240\241&#x27;</span>.decode(<span class="string">&#x27;utf-8&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Reversely, here is how to convert a text string to an</span></span><br><span class="line"><span class="comment"># &quot;escaped octal value&quot; string:</span></span><br><span class="line"><span class="string">&#x27;&#x27;</span>.join([ <span class="string">&#x27;\\&#x27;</span> + <span class="built_in">oct</span>(x)[<span class="number">2</span>:] <span class="keyword">for</span> x <span class="keyword">in</span> <span class="string">&#x27;学校&#x27;</span>.encode(<span class="string">&#x27;utf-8&#x27;</span>)])</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;In &lt;a href=&quot;https://protobuf.dev/&quot;&gt;protobuf&lt;/a&gt; text, a string can be shown in the following “escaped octal value” format:&lt;/p&gt;
&lt;figure cl</summary>
      
    
    
    
    
    <category term="python" scheme="https://gangmax.me/tags/python/"/>
    
    <category term="protobuf" scheme="https://gangmax.me/tags/protobuf/"/>
    
  </entry>
  
  <entry>
    <title>Python Zip Application</title>
    <link href="https://gangmax.me/blog/2024/08/29/python-zip-application/"/>
    <id>https://gangmax.me/blog/2024/08/29/python-zip-application/</id>
    <published>2024-08-29T15:42:00.000Z</published>
    <updated>2026-04-17T07:25:55.193Z</updated>
    
    <content type="html"><![CDATA[<p>From <a href="https://docs.python.org/3/library/zipapp.html">here</a> and <a href="https://stackoverflow.com/a/76027057/3115617">here</a>.</p><p>With “<a href="https://docs.python.org/3/library/zipapp.html">zipapp</a>“, you can create a executable Python archive with all its dependencies.</p><h1 id="Basic-Usage"><a href="#Basic-Usage" class="headerlink" title="Basic Usage"></a>Basic Usage</h1><p>Here is an example. Use the following Python file as the target script.</p><figure class="highlight python"><figcaption><span>inside.py</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> termcolor <span class="keyword">import</span> cprint</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello_inside</span>():</span><br><span class="line">    cprint(<span class="string">&#x27;Hello from zipapp inside...&#x27;</span>, <span class="string">&#x27;red&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    hello_inside()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><p>Run the following commands to create the “pyz” package file with “inside.py”.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> build</span><br><span class="line"><span class="built_in">mkdir</span> dist</span><br><span class="line"><span class="built_in">cp</span> inside.py build/</span><br><span class="line">pip install termcolor --target build</span><br><span class="line">python -m zipapp build --main inside:main --output dist/test.pyz</span><br></pre></td></tr></table></figure><p>After the “test.pyz” file is generated, you can run “python dist&#x2F;test.pyz” to execute the Python package.</p><h1 id="Advanced-Usage"><a href="#Advanced-Usage" class="headerlink" title="Advanced Usage"></a>Advanced Usage</h1><p>In some case, your code inside a “pyz” file needs to call external Python code out of the “pyz” file. Here is an example.</p><figure class="highlight python"><figcaption><span>inside.py</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> termcolor <span class="keyword">import</span> cprint</span><br><span class="line"><span class="keyword">from</span> outside <span class="keyword">import</span> hello_outside</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello_inside</span>():</span><br><span class="line">    cprint(<span class="string">&#x27;Hello from zipapp inside...&#x27;</span>, <span class="string">&#x27;red&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    hello_inside()</span><br><span class="line">    hello_outside()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><figure class="highlight python"><figcaption><span>outside.py</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> termcolor <span class="keyword">import</span> cprint</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello_outside</span>():</span><br><span class="line">    cprint(<span class="string">&#x27;Hello from outside...&#x27;</span>, <span class="string">&#x27;blue&#x27;</span>)</span><br></pre></td></tr></table></figure><p>Run the following commands to create the “pyz” package file with “inside.py” only.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> build</span><br><span class="line"><span class="built_in">mkdir</span> dist</span><br><span class="line"><span class="built_in">cp</span> inside.py build/</span><br><span class="line">pip install termcolor --target build</span><br><span class="line">python -m zipapp build --main inside:main --output dist/test.pyz</span><br></pre></td></tr></table></figure><p>Run the following command to execute the “test.pyz” file.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># &quot;/Users/auser/test/dist&quot; is where the &quot;outside.py&quot; resides.</span></span><br><span class="line"><span class="comment"># The &quot;PYTHONPATH&quot; environment variable sets the system path</span></span><br><span class="line"><span class="comment"># where Python looks for dependencies not in the &quot;pyz&quot; file.</span></span><br><span class="line">PYTHONPATH=/Users/auser/test/dist python dist/test.pyz</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;From &lt;a href=&quot;https://docs.python.org/3/library/zipapp.html&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://stackoverflow.com/a/76027057/3115617&quot;&gt;here&lt;/a&gt;</summary>
      
    
    
    
    
    <category term="python" scheme="https://gangmax.me/tags/python/"/>
    
    <category term="zipapp" scheme="https://gangmax.me/tags/zipapp/"/>
    
  </entry>
  
  <entry>
    <title>Create Ubuntu VM in UTM on Mac</title>
    <link href="https://gangmax.me/blog/2024/08/05/create-ubuntu-vm-in-utm-on-mac/"/>
    <id>https://gangmax.me/blog/2024/08/05/create-ubuntu-vm-in-utm-on-mac/</id>
    <published>2024-08-05T17:19:31.000Z</published>
    <updated>2026-04-17T07:25:55.193Z</updated>
    
    <content type="html"><![CDATA[<p>The content below records how to create an Ubuntu VM in <a href="https://getutm.app/">UTM</a> on Mac. From “<a href="https://docs.getutm.app/guides/ubuntu/">here</a>“. </p><ol><li><p>First download the image file of Ubuntu Server for ARM from <a href="https://ubuntu.com/download/server/arm">here</a>.</p></li><li><p>Following the <a href="https://docs.getutm.app/guides/ubuntu/#creating-a-new-virtual-machine">instructions</a> to create a VM instance in UTM and install Ubuntu Server on it with the image file downloaded in the first step.</p></li><li><p>By default, Ubuntu Server only has CLI. Install GUI with the following commands:</p></li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo apt update</span><br><span class="line">sudo apt install ubuntu-desktop</span><br><span class="line">sudo reboot</span><br></pre></td></tr></table></figure><ol start="4"><li>Following the instructions <a href="https://docs.getutm.app/guest-support/linux/">here</a> to enable clipboard and directory sharing. For example, install the following packages in the Ubuntu VM.</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install spice-vdagent qemu-guest-agent spice-webdavd</span><br></pre></td></tr></table></figure><p>Then you need to set UTM to enable the “clipboard&#x2F;directory” sharing. Go to “UTM -&gt; select the target UTM -&gt; Edit Selected VM -&gt; Sharing”, check the “Enable Clipboard Sharing” checkbox, and select the “SPICE WebDAV” item for the “Directory Share Mode” option.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;The content below records how to create an Ubuntu VM in &lt;a href=&quot;https://getutm.app/&quot;&gt;UTM&lt;/a&gt; on Mac. From “&lt;a href=&quot;https://docs.getutm.</summary>
      
    
    
    
    
    <category term="ubuntu" scheme="https://gangmax.me/tags/ubuntu/"/>
    
    <category term="utm" scheme="https://gangmax.me/tags/utm/"/>
    
  </entry>
  
  <entry>
    <title>SingleFile CLI Again</title>
    <link href="https://gangmax.me/blog/2024/08/02/singlefile-cli-again/"/>
    <id>https://gangmax.me/blog/2024/08/02/singlefile-cli-again/</id>
    <published>2024-08-02T18:44:48.000Z</published>
    <updated>2026-04-17T07:25:55.193Z</updated>
    
    <content type="html"><![CDATA[<p>The <a href="https://github.com/gildas-lormeau/single-file-cli/">SingleFile CLI</a> can be used to download the target web page into a single file with one command line. One of my previous post shows how to run it in Docker, while this post shows how to run it locally.</p><h3 id="Basic-Usage"><a href="#Basic-Usage" class="headerlink" title="Basic Usage"></a>Basic Usage</h3><p>First, you should download the execution file from <a href="https://github.com/gildas-lormeau/single-file-cli/releases">here</a>. Be sure to select the correct one for your “OS&#x2F;Arch”. For example, if you’re using Mac with silicon chip(ARM arch), you should select “single-file-aarch64-apple-darwin”. In this post I will use “Linux&#x2F;x86&#x2F;_64” in the command samples.</p><p>Before running, make sure Chrome or a Chromium-based browser is installed and can be found in default “PATH”. Otherwise you may need to set the “–browser-executable-path” option in the command line when executing “single-file”.</p><p>After you add the execution permission to the downloaded file, you can try running it.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Basic usage: single-file [url] [output] &lt;option*&gt;</span></span><br><span class="line"><span class="comment"># Runing without any arguments/options will print all the available options.</span></span><br><span class="line">./single-file-x86_64-linux https://imtx.me/ index.html</span><br></pre></td></tr></table></figure><h3 id="Advanced-Usage"><a href="#Advanced-Usage" class="headerlink" title="Advanced Usage"></a>Advanced Usage</h3><p>Sometimes the target web page needs login to view the content. In this case we need to save the “cookies” of the website from your browser, then use the “cookies” in the “single-file” running to make it work.</p><h4 id="Save-the-cookies"><a href="#Save-the-cookies" class="headerlink" title="Save the cookies"></a>Save the cookies</h4><p>You need to install some extension in your browser to save the target cookies. For example, in Chrome you can install the “<a href="https://chromewebstore.google.com/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm">Cookie-Editor</a>“ extension.</p><p>Open the target web page(login if it’s required), then open the “Cookie-Editor” extension, select “Export -&gt; JSON”, the cookies of this website for you will be saved into a local JSON file. Let’s say it’s “cookies.json”.</p><h4 id="Run"><a href="#Run" class="headerlink" title="Run"></a>Run</h4><p>Now you can download the target web page with the cookie file like below.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">./single-file-x86_64-linux --browser-cookies-file=&#x27;cookies.json&#x27; \</span><br><span class="line"> https://articles.zsxq.com/id_9getlhtuixe5.html venezeula.html</span><br></pre></td></tr></table></figure><p>Be careful with your cookie file especially not share it with anybody else, because that means anyone which has this cookie file can do all operations as you can do on the target website, just like he&#x2F;she can login the website with your account.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;The &lt;a href=&quot;https://github.com/gildas-lormeau/single-file-cli/&quot;&gt;SingleFile CLI&lt;/a&gt; can be used to download the target web page into a si</summary>
      
    
    
    
    
    <category term="html" scheme="https://gangmax.me/tags/html/"/>
    
    <category term="tool" scheme="https://gangmax.me/tags/tool/"/>
    
  </entry>
  
  <entry>
    <title>[转帖] 委内瑞拉：诅咒这个国家的，并不是资源</title>
    <link href="https://gangmax.me/blog/2024/08/02/re-venezuala/"/>
    <id>https://gangmax.me/blog/2024/08/02/re-venezuala/</id>
    <published>2024-08-02T11:28:45.000Z</published>
    <updated>2026-04-17T07:25:55.193Z</updated>
    
    <content type="html"><![CDATA[<p>作者：“<a href="https://wx.zsxq.com/dweb2/index/footprint/88882221222552">西塞罗</a>”</p><p>原文链接：“<a href="https://articles.zsxq.com/id_9getlhtuixe5.html">委内瑞拉：诅咒这个国家的，并不是资源</a>”</p><p>发表时间：2024年8月1日 23:43</p><hr><p>一个本该富得流油的土豪国，是怎么穷成拉美垫底。</p><h3 id="1"><a href="#1" class="headerlink" title="1"></a>1</h3><p>委内瑞拉乱了。</p><p>7月29日凌晨，委内瑞拉国家选举委员会宣布，现任总统马杜罗再次当选委内瑞拉总统。</p><p>选举结果出炉后，委内瑞拉多地发生了旨在抗议马杜罗选举舞弊的示威活动，并引发暴力冲突。</p><p>这次选举造成的影响可能会愈演愈烈，因为与上次马杜罗宣布胜选连任时的较小规模骚乱不同，此次委内瑞拉骚乱不仅蔓延境内各州，而且包括阿根廷、智利、哥斯达黎加、秘鲁、巴拿马、多米尼加和乌拉圭等拉美诸多“邻居”都宣布不承认马杜罗的胜选，马杜罗政权目前正处于空前的内外交困中。当然，马杜罗的选择是一条路走到黑，对内严厉镇压反对派，对外则宣布与几乎所有不承认此次大选结果的邻居们断交。</p><p>到当地时间31日，委内瑞拉总检长表示，已逮捕1062名参与暴力事件的人员，并且逮捕名单还在急速扩大当中。另有消息称一些地方的军警因为不愿意向抗议民众开枪，已经发生倒戈。委内瑞拉的政局，正处于关键时刻。</p><p>但实际上，无论能否成功“摆平”其境内的民众，度过此次危机，马杜罗政权的未来都不甚美妙。由于长期的经济崩溃弄得民怨沸腾，如果在此次选举中下台，其下场当然十分不妙。但即便能够再次弹压反对派，未来六年的执政期内，马杜罗依然将面临的是越发严重的经济崩溃态势。在其接棒查韦斯领导委内瑞拉的过去十年中，委内瑞拉经历了西半球国家中最严重的经济崩溃事件：石油收入暴跌93%，GDP缩水达到80%。尽管后疫情时代委内瑞拉经济有所恢复，但高达85%的年货币通胀率和逼近六成的极端贫困率依然困扰着这个国家。</p><p><strong>截至2017年，委内瑞拉的官方货币玻利瓦尔兑美元汇率自2000年以来已贬值92%。随着恶性通货膨胀的肆虐，马杜罗让货币进一步贬值95%，并在两年内将玻利瓦尔货币贬值11个零。</strong></p><p>为了“解决”经济危机，2017年以后，委政府干脆宣布拒绝公布了所有经济数据，所以该国目前到底经济破产到了什么地步，其实只能靠猜。</p><p>2021年，根据国际权威机构的评估，委内瑞拉的人均实际收入已经低于海地，成为整个拉美最贫穷的国家，截至当时，有96%的委内瑞拉人生活在贫困中，79%的人生活在联合国所定义的极端贫困当中。</p><p>委内瑞拉老百姓，现在真的穷得只剩下形同废纸的钱了。</p><p>国家破产到这个样子，大量还走的动态的人其实已经开始选择“用脚投票”，截至2023年，委内瑞拉流亡海外的难民人口达到了创纪录的770万人，而该国目前的人口总数据估计也就只有2890万人左右，<strong>也就是说，在过去的几年中，有超过四分之一的委内瑞拉人已经“用脚投票”离开了自己的祖国，创造了人类历史上非战争状态下的难民奇迹，且这些难民大部分都是年轻跑得动或有一定资产走得了的高价值人口。</strong></p><p>可以想见，如果马杜罗成功弹压了此次骚乱，看不到希望的委内瑞拉年轻人和仅存的技术人才恐怕会掀起又一轮“逃难潮”，等六年后再次大选时，该国还能剩下多少干的劳动的年轻人口都是个大问题。所以真的如其反对派领袖马查多所言，此次大选是“拯救祖国的最后机会”了。</p><p>此情此景，真的让人无法想象，如今沦为拉美最穷国的委内瑞拉，想当初曾经是拉美人均GDP最高的国度，那么这个本该土豪的国家是怎么混到今天这一步的呢？</p><h3 id="2"><a href="#2" class="headerlink" title="2"></a>2</h3><p>如果问世界第一石油土豪国是谁？很多人的第一反应会是沙特。但实际上，根据2022年的最新数据，在已探明石油储量中，沙特占全球总量17.2%，仅仅排在全球第二，排名第一的就是委内瑞拉，已探明石油储量占全球的17.8%，加上委内瑞拉人口少于沙特。如果按照“把石油卖了发给全国老百姓躺着挣钱”的简单逻辑，世界上第一石油土豪国，不应该是沙特，而是委内瑞拉才对。</p><p>可是这个世界的经济运行，并不是按照上述简单逻辑来的，这个世界上有一种奇怪的说法，叫做“资源的诅咒”，说得不那么玄乎，就是像石油这种大家都想要的资源，即便你国家地下有，它也是不可能自己流出来，自动换成美金，然后公平地分配给所有国民的。怎样组织开采挖掘，怎样在国际市场上换成钱，最终怎样公平地分配给罪有应得的人——更关键的是，你怎么定义这个钱谁该拿谁不该拿。这都相当考验人类的政治组织架构是否现代而合理，中间稍微有一点点差池，很可能就会让这个国家承受不住这份“颇天富贵”，而陷入万劫不复的深渊。这就是“资源的诅咒”的成因。</p><p>而在这方面的，中东石油富国由于其宗教所提供的超稳定结构和普惠教众的理念，在维持其君主制的同时，还可以至少暂时维系摊子不散。而欧洲的一些富产资源的小国，比如卢森堡、挪威等，则因为在开发资源前已经建立了完善的现代国家制度，能够保证资源的有序开采和普惠民众。</p><p>除了这两种模式之外，“资源的诅咒”在全世界其他地方确实屡次应验。因为开采并出售资源这件事本身，会极大地扩张资源拥有者的力量，这使得资源拥有者可以不顾多数人的意愿，绕开现代国家“纳税人的普遍同意”的构建原则，去建构一个威权体系。如果这个资源的拥有者首先是跨国公司，而后通过收买和支持当地军政府成为其打手买办，弹压民众，那就会演化为拉美的很多“香蕉共和国”。而如果这个资源的拥有者就是政府本身，则很可能演化成我们今天看到的委内瑞拉这副模样。</p><p>其实委内瑞拉这个国家很不幸，上述两种情况，它都经历过。委内瑞拉的石油发现非常之早，早在1936年，美国的壳牌石油公司就在该国发现了大量石油，并随机建立了石油开采体系。但当时的委内瑞拉正处于军政府的独裁统治之下，本来该国民主革命正风起云涌，推翻军政府在望，壳牌公司一发现石油，硬生生强行给军政府续了一口命——资本是逐利的，为了保证开采环境的稳定，壳牌公司开始资助委国军政府强行弹压民众，以方便石油的开采。于是委内瑞拉的第一民主化浪潮就这么硬生生被摁下去了。</p><p>但壳牌毕竟是美国的公司，它这样为了开采石油强行资助军政府，在美国国内也是承压严重的。尤其是二战以后，由于美苏争霸中意识形态的需求，委内瑞拉军政府能得到的支持进一步减少。终于在1958年，在民众的抗议中，军政府被迫放弃权力，委内瑞拉于同年举行了第一次全国普选。</p><p>一切至此为止看起来都非常美好，二十世纪六十年代，一度也成了委内瑞拉经济发展的黄金期，委内瑞拉人均GDP雄霸拉美的经济奇迹，也是在那一段时间发生的。</p><p>但委内瑞拉的不幸在于，有两种因素，如胎毒一般，在其走向民主的最初就被种下了。</p><p>首先，由于壳牌公司等美国能源企业在委内瑞拉民主化进程中支持军政府的不光彩角色，委内瑞拉的民意尤其是知识界民意从一开始就带有非常强烈的反美色彩。民主运动成功后，要求政府彻底驱逐美帝国主义资本、没收壳牌等公司在委内瑞拉石油企业中的股份的呼声一直不绝于耳。</p><p>其次，委内瑞拉的历届政府，确实也存在着一种非常强烈的没收外国资本，以充实国库的冲动。因为委内瑞拉1958年革命成功之后，曾经在民主革命道路上志同道合的各党，为了在大选中争取民意，都争相许诺自己在上台后会多发福利、多建医保，甚至国家拨款提高大学中的知识分子待遇。</p><p>可是如此众多的许诺都是花钱的，钱总需要有一个来源，于是左翼的政党就开始主张应当彻底没收“帝国主义资本”，用这种打土豪分田地的方式给本国民众发福利。</p><p>至此，驱除外国资本和福利主义这两种左翼主张在委内瑞拉完成了合流，这种看似非常合理的主张深得民心，让主张尊重私有产权的委国的右翼保守政党被骂为“卖国贼”而无从立足。</p><p>1969年的大选中，在经济建设上颇有建树的民主行动党遭遇了惨败，委内瑞拉的十年黄金发展期结束。</p><p>新上任基督教社会党总书记R·卡尔德拉·罗德里格斯刚刚就任总统，就采取了激进的左翼政策，对外废除美委贸易互惠条约、低价强行赎买外国公司在委内瑞拉的石油产业，并且将所有石油产业收归国有等做法。</p><p>对内，罗德里格斯则极力推行福利主义政策，包括推行公费医疗、提高国民养老保障、免费教育、落实八小时工作制、提高工人最低工资……凡此种种，所有你能想到的福利主义政策，基督教社会党在上世纪70年代都在委内瑞拉搞过一遍。</p><p>**需要指出的是，基社党当时在委内瑞拉的这些举措，虽然今天看来非常像胡搞，可是在上世纪下半叶的拉美其实非常常见。**拉美流行的天主教思想本来就有非常浓厚的救济穷人的情怀，20世纪下半叶又与当时普遍流行的社会主义思潮相结合，在当时风靡一时。我曾经讲过阿根廷的故事，对比阿根廷贝隆夫妇执政时代所推行的社会改革运动，你会发现委内瑞拉基社党搞的这一套跟贝隆夫妇其实是如出一辙的。</p><p>但阿根廷不幸中的幸运在于，它没有石油这么强力的国际硬通货为政府的大型国有化和财政冒进背书，贝隆主义那一套搞上个几年，政府财政就面临破产，国内则民生凋敝，然后就会发生政权轮替。左右翼轮流上台让阿根廷左两年、右三年，虽然日子过得很苦，但好歹不会跌进最彻底的深渊。</p><p>但委内瑞拉就不同了，它幸运中的不幸就是，石油这玩意儿实在是太好卖了。70年代政府收归国有之后，委内瑞拉的石油开采技术和效率虽然已经逐渐开始与国际水平出现脱钩，但基社党政府幸运地赶上了（更确切地说，是参与制造了）70年代的石油危机，国际原油价格的高企让委内瑞拉赚了个盆满钵满。委内瑞拉老百姓得到了一个错误的经验，觉得罗德里格斯的这套主张不仅走得通，而且还挺好。</p><p>从此福利主义就成了委内瑞拉各党在竞选中都必须遵守的一条死线，因为民众的胃口已经被那次偶然“养刁了”。</p><p>可是在貌似的人间天堂背后，日后促成委内瑞拉崩解的绝症开始滋生。</p><p>首当其冲的就是官员腐败的问题，石油产业收归国有，和大量资金投入福利政策，让委内瑞拉政府获得了空前的财权，这对于一个还不成熟的现代政府来说本来就非常容易滋生腐败。更致命的是，70年代的委内瑞拉还丧失了抵抗腐败的最好保鲜剂——舆论的监督。</p><p>由于基社党政府同期执行的石油产业国有化、高福利政策受到了反对党的批评，政府得以借助民族主义情绪关停了那些批判最为猛烈的媒体，失去舆论监督的政府于是在石油高收入、高福利的滋养下变得越发腐败。</p><p>而在有钱就主要搞腐败、其次发福利的狂欢中，委内瑞拉也没有利用实有收入建立国家主权基金这样长远的投资行为。财政一直处于紧平衡的状态下。</p><p>在70年代，委内瑞拉受惠于国际的高油价，这一切问题还并不显现。</p><p>可是到了80年代以后，随着油价的下跌，委内瑞拉的政府收入急速减少，但福利和腐败的刚需却依然存在，危机一下子就凸显了。</p><p>当民众感受到自己的日子过得愈发艰难，又不愿意承认这是自己当初选择赶走外国资本、敦促政府搞高福利政策的错误造成的恶果时，一种莫名的焦躁情绪就在委内瑞拉国内蔓延。而基社党那种普通的左翼主张此时已经不能满足国民的需求了，另一种更为激进的主张取而代之，粉墨登场。这就是所谓的“查韦斯主义”。</p><h3 id="3"><a href="#3" class="headerlink" title="3"></a>3</h3><p>1997年，查韦斯创建了“第五共和国运动”，1998年正式宣布参选总统，誓言消除国内两大政党（基督教社会党和民主行动党）的政治腐败，扬言打击“掠夺人民的寡头政治家”，深受贫穷老百姓的认同与支持。同年12月，查韦斯获得逾半数选票，成为委内瑞拉史上最年轻的总统，查韦斯时代开始了。</p><p>查韦斯在在野时代就猛烈地抨击基督教社会党的改革——但他的批评不是说基社党搞得太激进了，而是不够彻底。查韦斯认为基社党执政时期的政府腐败和国家财政困难、经济凋敝，都是来源于其改革的不彻底性——基社党虽然清除了美国的资本，但委内瑞拉在文化、政治制度上依然有很多美国式的元素，想要脱困，就必须清除这些“余毒”，走他提出的那条新路。</p><p>那么这条路究竟是怎样的呢？贯穿查韦斯执政纲领中，有个非常至关重要的单词，名叫“使命”。查韦斯将自己的整个计划称为“玻利瓦尔使命”，而在这个总体“使命”之下，又分为30多个分支使命，而且多数都以拉美革命先贤命名，有着异常复杂的名字，这些使命统领指挥社会的方方面面，每一个使命都有非常具体的分支目标，其中某些使命给老百姓许诺也非常美好。比如反腐败的使命许诺彻底清除百姓痛恨贪官，“一个不留”；有关石油经济的“使命”则旨在掌握“完全石油主权”，把基社党留下的那一丁点民间和外资参股的空间也清除掉了；医疗方面的使命许诺老百姓免费看得起病；住房方面的使命许诺年轻人都住得起房——一切看上去都是基社党当年许诺的翻倍，如此的美好，一下子就把老百姓忽悠住了。</p><p><strong>而在现实逻辑层面，查韦斯所提出的“玻利瓦尔使命”最重要的突破，是它让行政权可以借此突破法律乃至宪法的制约。</strong></p><p>原本基社党搞的那些革新虽然也十分激进，但受制于委内瑞拉的国家体系，是需要一步步通过修改法律、释放行政政策来的。可是查韦斯用“使命”成功突破了这种束缚。很多时候，虽然法条是这样写的，但查总统一拍脑门，说我提个什么什么使命，一下子就把这个法条绕开了。总之就是使命先行，法律靠边站。</p><p>这样一搞，就让原本还能在委内瑞拉勉强经营的内外资本变得惶惶不可终日，因为资本的长期投资是稳定性的，如果政府明天就可以通过某个“使命”绕开法律，把你这摊子生意以委内瑞拉人民的名义连锅端走。那甭说石油，小买卖也没法在这种环境下生存。</p><p>于是查韦斯时代首先发生的就是资本的成批出逃。委内瑞拉财政收入的单一性开始加剧，石油收入从1998年的70%一路飙涨到马杜罗时代的98%，而1999年委内瑞拉有49万个私人公司，到2018年，只剩了28万个。</p><p>简而言之，就是除了国家“掌握完全主权”的石油产业，委内瑞拉已经没有什么能赚钱的营生了。</p><p>此外，也许是因为在外资的奶牛已经被榨干之后，只能通过侵害中产的利益才能够填饱底层的碌碌饥肠，或者超前认识到了这群有知识的人最可能成为自己的反对者。查韦斯的使命计划还打击委内瑞拉几乎所有行业中的中产。比如石油行业的相关“使命”降低了石油工程师的工资，医疗行业的“使命”则发布了对医生的限薪令，文化产业的“使命”缄口了一切对其狂想冷嘲热讽的作家和记者……</p><p>所有这些行业的中层精英在被限制收入和发声后，不约而同地选择了一条路，那就是离开。所以在21世纪的头十年，委内瑞拉首先发生的，就是社会精英阶层的大量“蒸发”。</p><p>而对于最喜欢福利政策的底层百姓，查韦斯则创新性地提出了“弗洛伦蒂诺使命”，这个使命特别值得拿来一说的原因，是因为它给委内瑞拉的每个社区都派驻了“指导员”，名义上是指导这些社区的民众免受“资本家的蛊惑和侵蚀”，而事实上，这些人的主要任务是旨在记录并上报那些反对查韦斯政策、并公开表示下次不投票给他的老百姓，一旦你在这个名单上“榜上有名”。查韦斯政府一般也不会怎样暴力镇压你，只不过查韦斯政府许诺给老百姓的一切福利，你就都别想了。</p><p>查韦斯这样搞的理由冠冕堂皇：福利是发放给委内瑞拉人民的，你反对我，就不属于委内瑞拉人民，不属于人民你还领个屁的福利？</p><p><strong>想享受医保吗？想领福利补贴么？想领就必须支持查韦斯、马杜罗。</strong></p><p>从这个角度看，我其实咬不准马杜罗这次选举到底得了多少真实选票，因为在年轻人批量逃离之后，委内瑞拉想靠着政府救济凑合吧这辈子过完的老年人，确实占了相当比例。</p><p>资本和中产外逃，底层民众受到政府福利的绑架，按理说查韦斯的这套改革政策给委内瑞拉的负面效果应该是立竿见影的。</p><p>可是，关键时刻，石油给予委内瑞拉的“镇痛作用”再次呈现了。</p><p>查韦斯的运气是真的好，他执政的十几年刚好赶上了国际油价再次一飞冲天的时期。这就导致了委内瑞拉的经济非但没有在这期间崩溃，反而维持着增长。</p><p>但这段时期之内，不认同查韦斯政策理念的委内瑞拉精英阶层纷纷出走，而将经济增长错误地归功于查韦斯新政并感恩戴德的那批人则留了下来。二十世纪七十年代的那一幕，在委内瑞拉再度上演了。</p><p>但委内瑞拉此时的问题，其实更严重了。查韦斯的新政，让政府的财权变得更加扩张、腐败问题则在财政扩张的激励下实际上变得更加严重，资本和中产的出逃也让委内瑞拉失去了发展石油以外的产业的机会，国有化和技术能力的停滞则进一步制约了石油产业的开采效率……</p><p>所有这些，导致委内瑞拉想要维持稳定，对原油价格维持在高位的要求变得更加苛刻而脆弱。委内瑞拉经不起半点石油价格的下跌，因为一旦油价下跌发声，这个体系顷刻间就会崩溃。</p><p>而这种早晚会发生的“倒霉事”，最终就发生在了查韦斯去世，马杜罗接任的时代。</p><h3 id="4"><a href="#4" class="headerlink" title="4"></a>4</h3><p>实话实说，马杜罗此人的运气真的是不好，他刚刚接任的2014年，国际原油价格就从一百美金一桶的高位腰斩到了44美金。查韦斯时代所积累的所有隐患，在这一刻被集中引爆，当年委内瑞拉就因为外汇收支的不平衡发生了严重的粮食短缺，平均每个国民减重8.7公斤。但马杜罗作为查韦斯的接班人，他的一切执政合法性来自对查韦斯的继承，面对这种困局，其所选择的只能是硬着头皮，将福利主义进行到底，不顾政府收支的不平衡，加大开支给民众发放粮食补贴，以维持稳定的局面。</p><p>可是这种寅吃卯粮本来就难以长时间维系，加之查韦斯时代留下的另一个恶果在此时也发作了——</p><p>由于常年执行反精英主义政策，再加上选举生态的不健康，委内瑞拉的专业精英们不是选择了移民，就是被排除在决策体系之外。马杜罗时代作出的诸多决策总是呈现出一种高度的不专业性。</p><p>**而查韦斯的“使命”体系又恰恰是极度依赖政府的能动施为的。**这就导致了马杜罗政府为了解决危机总是最终采取那些火上浇油的盲动和乱动，每每“好心办坏事”，总是进一步加剧了危机。</p><p>比如2017年灾难性的币制改革，几乎让官方货币玻利瓦尔成为废纸，民间本币交易体系已经完全崩溃了，老百姓只能用黑市上换得的美元进行交易，而委内瑞拉政府此时又更灾难性地加强了货币管制，让经济危机更加雪上加霜。</p><p>于是原本查韦斯时代，从委内瑞拉出逃的主力还只是内外资本和工程师、医生、高级知识分子等中产上层，但到了马杜罗时代，中产下层乃至但凡不愿意接受这套“福利体系”绑架的委内瑞拉年轻人也纷纷出走，由此才创造了前文提到的，一个国家在和平时代对外输出772万难民，四分之一多的国民流亡海外的奇景。</p><p>说来真的很讽刺，委内瑞拉，这个国家没有战争，有的只有被称为“21世纪黄金”的石油。然而这份上天的馈赠，让委内瑞拉人一再选择看上去很美的激进左翼福利政策，并且为了与这套政策相配套，支持了一个更比一个激进的威权政府，希望像查韦斯一样的强人、伟人，来公平地帮他们分配能从这份礼物中获得的利益。</p><p>然而，迎接委内瑞拉人的，是一次更比一次惨痛的困局。</p><p>如今委内瑞拉又出现了动乱，这个国家能走出这种死循环吗？我并不看好。因为在上述的长期折腾当中，能够给这个国家带来真正活力和附加值的中上层精英已经被蒸发掉了。还留在委内瑞拉国内的，大都是一些实在走不了，或者依然迷信福利政策的人——虽然马杜罗政府能给这批人的福利，也已经只够糊口而已了。而马杜罗政府为了解释其经济上的失败，近年来正在加剧仇外尤其是仇美教育。更将一切反对者指责为与西方相勾结。</p><p>而由于海量的石油储备，以及求得国际外援的可能性，马杜罗的日子虽然已经较为难过，却未必一定是过不下去的。摆平这次骚乱的可能性依然存在。而骚乱过后，随着又一批失望的人离开，委内瑞拉会剩下一批更加被福利所绑架，并且认同或者被迫认同马杜罗政府理念的民众。社会整体民意有可能进一步左转、并趋向于民粹化。</p><p>所以简而言之，资源的诅咒，如果这种东西真的存在的话，它依然可能在委内瑞拉的上空飘荡很久，但当我讲完这个故事之后，你回首再看，你能说，诅咒那个国家的，真的是资源么？</p><p>不是的，是人们被那黑金所激起的，对乌托邦过于天真而急切的向往，和随之而来的野心、偏激和妄念。</p><p>委内瑞拉，诅咒这个国度的，根本不是资源。</p><p>(全文完)</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;作者：“&lt;a href=&quot;https://wx.zsxq.com/dweb2/index/footprint/88882221222552&quot;&gt;西塞罗&lt;/a&gt;”&lt;/p&gt;
&lt;p&gt;原文链接：“&lt;a href=&quot;https://articles.zsxq.com/id_9getlh</summary>
      
    
    
    
    
    <category term="record" scheme="https://gangmax.me/tags/record/"/>
    
    <category term="news" scheme="https://gangmax.me/tags/news/"/>
    
  </entry>
  
  <entry>
    <title>Shared Directory of UTM</title>
    <link href="https://gangmax.me/blog/2024/06/19/shared-directory-of-utm/"/>
    <id>https://gangmax.me/blog/2024/06/19/shared-directory-of-utm/</id>
    <published>2024-06-19T04:06:01.000Z</published>
    <updated>2026-04-17T07:25:55.193Z</updated>
    
    <content type="html"><![CDATA[<p>From <a href="https://docs.getutm.app/guest-support/sharing/directory/">here</a> and <a href="https://docs.getutm.app/guest-support/linux/">here</a>.</p><p>This post records the steps that in “<a href="https://getutm.app/">utm</a>“ how to make a Linux guest running on a Mac host be able to access the shared directory.</p><h2 id="Host-setup"><a href="#Host-setup" class="headerlink" title="Host setup"></a>Host setup</h2><p>First we need to enable the shared directory feature on host. Open the “utm” app, select the target guest VM and open the “Edit selected VM” dialog(the “upper-right” button). In the “Sharing” section, select the “SPICE WebDAV” item in the “Directory Share Mode” field, and the shared directory in the “Path” field, then “Save”.</p><h2 id="Guest-setup"><a href="#Guest-setup" class="headerlink" title="Guest setup"></a>Guest setup</h2><p>From <a href="https://github.com/simonchen/davfs2">here</a>, <a href="https://bbs.archlinux.org/viewtopic.php?id=269826">here</a> and <a href="https://linuxize.com/post/how-to-add-user-to-group-in-linux/">here</a>.</p><h3 id="1-Install-the-required-packages-first"><a href="#1-Install-the-required-packages-first" class="headerlink" title="1. Install the required packages first"></a>1. Install the required packages first</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># When installing &quot;davfs2&quot;, it asked whether you want to mount by non-root user,</span></span><br><span class="line"><span class="comment"># I selected yes.</span></span><br><span class="line">sudo apt install spice-webdavd qemu-guest-agent davfs2</span><br></pre></td></tr></table></figure><h3 id="2-Create-the-target-directory"><a href="#2-Create-the-target-directory" class="headerlink" title="2. Create the target directory"></a>2. Create the target directory</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> /home/auser/exchange</span><br></pre></td></tr></table></figure><p>Update the “&#x2F;etc&#x2F;fstab” file by adding the following content.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://127.0.0.1:9843       /home/auser/exchange         davfs           rw,user,uid=auser,noauto 0 0</span><br></pre></td></tr></table></figure><h3 id="3-Mount"><a href="#3-Mount" class="headerlink" title="3. Mount"></a>3. Mount</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Restart service to reload &quot;/etc/fstab&quot;.</span></span><br><span class="line">systemctl daemon-reload</span><br><span class="line"></span><br><span class="line"><span class="comment"># Add the current user to the &quot;davfs2&quot; group.</span></span><br><span class="line">sudo usermod -a -G davfs2 auser</span><br><span class="line"></span><br><span class="line"><span class="comment"># Mount.</span></span><br><span class="line"><span class="comment"># I had to restart the guest OS to run this command since it reported</span></span><br><span class="line"><span class="comment"># &quot;auser must be in group davfs2&quot; although it&#x27;s already added. Restarting</span></span><br><span class="line"><span class="comment"># will make it take effect I think?</span></span><br><span class="line">mount /home/auser/exchange</span><br><span class="line"><span class="comment"># It asks for the &quot;username/password&quot; to mount, since need account is needed</span></span><br><span class="line"><span class="comment"># in this case, just enter to go next. If everything works, you should be able</span></span><br><span class="line"><span class="comment"># to see the the content mounted(shared) directory and you can also write files</span></span><br><span class="line"><span class="comment"># in it.</span></span><br></pre></td></tr></table></figure><p>After that, everytime the system is started, you can run “mount &#x2F;home&#x2F;auser&#x2F;exchange” to mount the shared directory.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;From &lt;a href=&quot;https://docs.getutm.app/guest-support/sharing/directory/&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://docs.getutm.app/guest-support/linux</summary>
      
    
    
    
    
    <category term="utm" scheme="https://gangmax.me/tags/utm/"/>
    
    <category term="webdav" scheme="https://gangmax.me/tags/webdav/"/>
    
  </entry>
  
  <entry>
    <title>git lfs</title>
    <link href="https://gangmax.me/blog/2024/05/07/git-lfs/"/>
    <id>https://gangmax.me/blog/2024/05/07/git-lfs/</id>
    <published>2024-05-07T16:30:11.000Z</published>
    <updated>2026-04-17T07:25:55.192Z</updated>
    
    <content type="html"><![CDATA[<p>From <a href="https://git-lfs.com/">here</a>.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># The issue happens when a file is too big to save into git repo.</span></span><br><span class="line">git push origin main</span><br><span class="line">Enumerating objects: 7, <span class="keyword">done</span>.</span><br><span class="line">Counting objects: 100% (7/7), <span class="keyword">done</span>.</span><br><span class="line">Compressing objects: 100% (4/4), <span class="keyword">done</span>.</span><br><span class="line">Writing objects: 100% (4/4), 653.14 KiB | 358.00 KiB/s, <span class="keyword">done</span>.</span><br><span class="line">Total 4 (delta 2), reused 0 (delta 0), pack-reused 0</span><br><span class="line">remote: Resolving deltas: 100% (2/2), completed with 2 <span class="built_in">local</span> objects.</span><br><span class="line">remote: warning: See https://gh.io/lfs <span class="keyword">for</span> more information.</span><br><span class="line">remote: warning: File cache.db is 69.36 MB; this is larger than GitHub<span class="string">&#x27;s recommended maximum file size of 50.00 MB</span></span><br><span class="line"><span class="string">remote: warning: GH001: Large files detected. You may want to try Git Large File Storage - https://git-lfs.github.com.</span></span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. Install &quot;git-lfs&quot;.</span></span><br><span class="line">sudo apt-get install git-lfs</span><br><span class="line"><span class="comment"># 2. Setup &quot;git-lfs&quot;.</span></span><br><span class="line">git lfs install</span><br><span class="line">git lfs track <span class="string">&quot;cache.db&quot;</span></span><br><span class="line">git add .gitattributes</span><br><span class="line"><span class="comment"># 3. Do git operations as usual.</span></span><br><span class="line">git add .</span><br><span class="line">git commit -m <span class="string">&#x27;Add the latest content.&#x27;</span></span><br><span class="line">git push origin main</span><br></pre></td></tr></table></figure><p>The core Git LFS idea is that instead of writing large blobs to a Git repository, only a pointer file is written. More details can be found in the <a href="https://github.com/git-lfs/git-lfs/blob/main/docs/spec.md">offline document</a>.</p><hr><p>Added on 2024-06-05.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Retrieve original files after a git clone with git lfs. From:</span></span><br><span class="line"><span class="comment"># https://stackoverflow.com/a/72830455/3115617</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Fetch all the LFS files from the remote.</span></span><br><span class="line">git lfs fetch</span><br><span class="line"></span><br><span class="line"><span class="comment"># Replace the pointer files to the actual files.</span></span><br><span class="line">git lfs checkout</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;From &lt;a href=&quot;https://git-lfs.com/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;</summary>
      
    
    
    
    
    <category term="git" scheme="https://gangmax.me/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>Set &quot;pip&quot; Mirror</title>
    <link href="https://gangmax.me/blog/2024/04/25/set-pip-mirror/"/>
    <id>https://gangmax.me/blog/2024/04/25/set-pip-mirror/</id>
    <published>2024-04-25T11:24:29.000Z</published>
    <updated>2026-04-17T07:25:55.192Z</updated>
    
    <content type="html"><![CDATA[<p>From <a href="https://mirrors.sustech.edu.cn/help/pypi.html">here</a>.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pip install --upgrade pip --index-url https://mirrors.sustech.edu.cn/pypi/web/simple</span><br><span class="line">pip config <span class="built_in">set</span> global.index-url https://mirrors.sustech.edu.cn/pypi/web/simple</span><br></pre></td></tr></table></figure><p>The second command creates a “~&#x2F;.config&#x2F;pip&#x2F;pip.conf” file with the following content, which will be read by pip when running.</p><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[global]</span></span><br><span class="line"><span class="attr">index-url</span> = https://mirrors.sustech.edu.cn/pypi/web/simple</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;From &lt;a href=&quot;https://mirrors.sustech.edu.cn/help/pypi.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;</summary>
      
    
    
    
    
    <category term="python" scheme="https://gangmax.me/tags/python/"/>
    
    <category term="pip" scheme="https://gangmax.me/tags/pip/"/>
    
  </entry>
  
  <entry>
    <title>Pyinstaller Notes</title>
    <link href="https://gangmax.me/blog/2024/04/24/pyinstaller-notes/"/>
    <id>https://gangmax.me/blog/2024/04/24/pyinstaller-notes/</id>
    <published>2024-04-24T19:50:35.000Z</published>
    <updated>2026-04-17T07:25:55.192Z</updated>
    
    <content type="html"><![CDATA[<p>Recently I’m using “pyinstaller” and here are some notes from my recent experience.</p><h2 id="How-to-include-Python-files-loaded-dynamically"><a href="#How-to-include-Python-files-loaded-dynamically" class="headerlink" title="How to include Python files loaded dynamically?"></a>How to include Python files loaded dynamically?</h2><p>From <a href="https://github.com/pyvisa/pyvisa-py/issues/302#issuecomment-875306359">here</a> and <a href="https://pyinstaller.org/en/stable/usage.html#cmdoption-hidden-import">here</a>.</p><p>In my code I use “importlib.import_module(…)” to load module dynamically. After packaged with “pyinstaller”. Running the program gets error that the target module cannot be found. The reason is, “pyinstaller” will parse the Python code and only include the modules explicitly imported.</p><p>To fix the issue, I can either:</p><ol><li><p>Add explicit “import” statement in Python code(it’s OK not to use the explicitly imported module since the dynamically importing will work anyway);</p></li><li><p>Add the “–hidden-import” argument(s) when executing “pyinstaller”, such as:</p></li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pyinstaller --hidden-import <span class="string">&#x27;tap.commands&#x27;</span> --hidden-import <span class="string">&#x27;tap.commands.id&#x27;</span> tap.py</span><br></pre></td></tr></table></figure><p>I choose option 1 since adding the explicit “import” statements is much clearer than updating the “pyinstaller” command.</p><h2 id="How-to-package-data-resource-files"><a href="#How-to-package-data-resource-files" class="headerlink" title="How to package data(resource) files?"></a>How to package data(resource) files?</h2><p>From <a href="https://stackoverflow.com/questions/41870727/pyinstaller-adding-data-files">here</a> and <a href="https://pyinstaller.org/en/stable/spec-files.html#adding-data-files">here</a>.</p><p>In my code, some data files locates at a project package which are loaded on runtime. The code works file until it’s running within the executable file compiled by “pyinstaller”. The reason is that such data files are not included when “pyinstaller” does the packaging. To fix this issue, 2 steps are required:</p><ol><li>Add the “–add-data” argument when executing “pyinstaller”, such as:</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pyinstaller --add-data <span class="string">&quot;tap/commands/*.template:tap/commands&quot;</span> tap.py</span><br></pre></td></tr></table></figure><ol start="2"><li>Update project code by using “pkgutil” to read the data file instead of reading the target file directly:</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">import pkgutil</span><br><span class="line">template_bin = pkgutil.get_data(<span class="string">&#x27;tap&#x27;</span>, <span class="string">&#x27;commands/id.template&#x27;</span>)</span><br><span class="line">template = template_bin.decode(<span class="string">&#x27;UTF-8&#x27;</span>, <span class="string">&#x27;ignore&#x27;</span>)</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Packaging everything into one executable file makes running a Python program much easier, but it breaks some fundamental assumptions how a Python program runs, which brings some extra cost to make it work.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Recently I’m using “pyinstaller” and here are some notes from my recent experience.&lt;/p&gt;
&lt;h2 id=&quot;How-to-include-Python-files-loaded-dynami</summary>
      
    
    
    
    
    <category term="python" scheme="https://gangmax.me/tags/python/"/>
    
    <category term="pyinstaller" scheme="https://gangmax.me/tags/pyinstaller/"/>
    
  </entry>
  
  <entry>
    <title>Use &quot;date&quot; Command in File Name Filtering</title>
    <link href="https://gangmax.me/blog/2024/03/27/use-date-command-in-file-name-filtering/"/>
    <id>https://gangmax.me/blog/2024/03/27/use-date-command-in-file-name-filtering/</id>
    <published>2024-03-27T12:14:26.000Z</published>
    <updated>2026-04-17T07:25:55.192Z</updated>
    
    <content type="html"><![CDATA[<p>Imagining the following scenario. You have a backup file created automatically each day. You want to have a cron job to remove the outdated backup files. A problem emerges: how to write a command to remove the outdated backup files and keep the ones which are not outdated?</p><p>The backup files are named with date like “backup-2024-03-26.tar.gz”. Then the problem becomes: how to select the files whose name matche the outdated pattern? The answer comes from the “date” command.</p><p>Let’s say the clean-up command runs in every 15th day of a month, and it removes the backup files created in the last month, which keeps the ones created in the current month. We just need to use the following “rm” command:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">rm</span> backup-$(<span class="built_in">date</span> --<span class="built_in">date</span>=<span class="string">&#x27;1 month ago&#x27;</span> +<span class="string">&#x27;%Y-%m&#x27;</span>)*.tar.gz</span><br></pre></td></tr></table></figure><p>Note that the “date –date&#x3D;’1 month ago’ +’%Y-%m’” part returns “2024-02” when the current date is “2024-03-xx”. So this command will just work as expected. More details about how to use the “date” command can be found <a href="https://www.cyberciti.biz/tips/linux-unix-get-yesterdays-tomorrows-date.html">here</a>.</p><p>To make this command run every 15th day of a month, add the command line below after running “cronjob -e”:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># The cron schedule expresion below means &quot;At 09:30 on day-of-month 15&quot;.</span></span><br><span class="line"><span class="comment"># From: https://crontab.guru/</span></span><br><span class="line">30 9 15 * * <span class="built_in">rm</span> /home/user/backups/backup-$(<span class="built_in">date</span> --<span class="built_in">date</span>=<span class="string">&#x27;1 month ago&#x27;</span> +<span class="string">&#x27;%Y-%m&#x27;</span>)*.tar.gz</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Imagining the following scenario. You have a backup file created automatically each day. You want to have a cron job to remove the outdat</summary>
      
    
    
    
    
    <category term="linux" scheme="https://gangmax.me/tags/linux/"/>
    
    <category term="command" scheme="https://gangmax.me/tags/command/"/>
    
  </entry>
  
  <entry>
    <title>MacOS Clean Up</title>
    <link href="https://gangmax.me/blog/2024/03/21/macos-clean-up/"/>
    <id>https://gangmax.me/blog/2024/03/21/macos-clean-up/</id>
    <published>2024-03-21T11:20:10.000Z</published>
    <updated>2026-04-17T07:25:55.192Z</updated>
    
    <content type="html"><![CDATA[<p>Today I did some disk space cleanup on Mac. I used “<a href="https://en.wikipedia.org/wiki/OmniDiskSweeper">OmniDiskSweeper</a>“ to find the target files, which is a freeware disk space analyzer utility for macOS.</p><h2 id="Clean-old-version-IntelliJ-cache-files"><a href="#Clean-old-version-IntelliJ-cache-files" class="headerlink" title="Clean old version IntelliJ cache files"></a>Clean old version IntelliJ cache files</h2><p>I find “IntelliJ IDEA” has a lot of old version cache files in the “~&#x2F;Library&#x2F;ApplicationSupport&#x2F;JetBrains” directory. And there’s a safe way to clear them(from <a href="https://youtrack.jetbrains.com/issue/IDEA-287210/Clean-old-version-intellij-cache-automatically">here</a>).</p><p>Open “IntelliJ IDEA”, go to “Help -&gt; Delete Leftover IDE Directories”, you can do the clearance safely.</p><h2 id="Clean-up-“Homebrew”-cache-files"><a href="#Clean-up-“Homebrew”-cache-files" class="headerlink" title="Clean up “Homebrew” cache files"></a>Clean up “Homebrew” cache files</h2><p>From <a href="https://github.com/Homebrew/brew/issues/3784#issuecomment-364675767">here</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">trash $(brew --cache)</span><br></pre></td></tr></table></figure><p>Note that “<a href="https://formulae.brew.sh/formula/trash">trash</a>“ is a Homebrew formulae and CLI tool that moves files or folder to the trash.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Today I did some disk space cleanup on Mac. I used “&lt;a href=&quot;https://en.wikipedia.org/wiki/OmniDiskSweeper&quot;&gt;OmniDiskSweeper&lt;/a&gt;“ to find </summary>
      
    
    
    
    
    <category term="mac" scheme="https://gangmax.me/tags/mac/"/>
    
  </entry>
  
  <entry>
    <title>SingleFile CLI</title>
    <link href="https://gangmax.me/blog/2024/02/29/singlefile-cli/"/>
    <id>https://gangmax.me/blog/2024/02/29/singlefile-cli/</id>
    <published>2024-02-29T12:51:19.000Z</published>
    <updated>2026-04-17T07:25:55.192Z</updated>
    
    <content type="html"><![CDATA[<p>From <a href="https://github.com/gildas-lormeau/single-file-cli">here</a>.</p><p><a href="https://github.com/gildas-lormeau/SingleFile">SingleFile</a> is software to save a complete web page into a single HTML file(via browser extension and a CLI tool). The CLI version “<a href="https://github.com/gildas-lormeau/single-file-cli">single-file-cil</a>“ can be used as below.</p><p>Note that the instructions below show how to install and use this CLI tool with Docker which is the simplest way, however it requires you have Docker installed. You can also run from code without Docker, but it requires more work to do, such as installing the dependencies manually.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Pull the Docker image of &quot;single-file-cil&quot;.</span></span><br><span class="line">docker pull capsulecode/singlefile</span><br><span class="line"><span class="comment"># Create a tag for the Docker image.</span></span><br><span class="line">docker tag capsulecode/singlefile singlefile</span><br><span class="line"><span class="comment"># Run the tool by given the web page URL and save it to a file.</span></span><br><span class="line">docker run singlefile <span class="string">&quot;https://mp.weixin.qq.com/s/_MQmML08NWAtbhf8uxvZUg&quot;</span> &gt; wechat.html</span><br></pre></td></tr></table></figure><p>To better understand how the Docker image works, you can read the “Dockerfile” <a href="https://github.com/gildas-lormeau/single-file-cli/blob/master/Dockerfile">here</a>.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;From &lt;a href=&quot;https://github.com/gildas-lormeau/single-file-cli&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/gildas-lormeau/SingleFile&quot;&gt;</summary>
      
    
    
    
    
    <category term="html" scheme="https://gangmax.me/tags/html/"/>
    
    <category term="tool" scheme="https://gangmax.me/tags/tool/"/>
    
  </entry>
  
  <entry>
    <title>Git: Create a Local Branch from Remote Branch</title>
    <link href="https://gangmax.me/blog/2024/01/05/git-create-a-local-branch-from-remote-branch/"/>
    <id>https://gangmax.me/blog/2024/01/05/git-create-a-local-branch-from-remote-branch/</id>
    <published>2024-01-05T15:52:50.000Z</published>
    <updated>2026-04-17T07:25:55.192Z</updated>
    
    <content type="html"><![CDATA[<p>Some useful git command about branch are listed below, from <a href="https://stackoverflow.com/a/60210807/3115617">here</a> and <a href="https://www.freecodecamp.org/news/git-delete-remote-branch/">here</a>.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Create a new local branch based on an existing remote branch.</span></span><br><span class="line">git checkout -b new-branch-name --track origin/master</span><br><span class="line"></span><br><span class="line"><span class="comment"># Cherry pick existing commit to the crrent branch.</span></span><br><span class="line">git cherry-pick 0bf06d7dd0b2644b7f0f8f4a5f6f24447da34842</span><br><span class="line"></span><br><span class="line"><span class="comment"># Remove a remote branch.</span></span><br><span class="line">git push origin -d name_of_a_branch</span><br><span class="line"></span><br><span class="line"><span class="comment"># Remove a local branch.</span></span><br><span class="line">git branch -d name_of_a_branch</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Some useful git command about branch are listed below, from &lt;a href=&quot;https://stackoverflow.com/a/60210807/3115617&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;</summary>
      
    
    
    
    
    <category term="git" scheme="https://gangmax.me/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>Google Photos is stuck on &quot;preparing backup&quot;</title>
    <link href="https://gangmax.me/blog/2024/01/04/google-photos-is-stuck-on-preparing-backup/"/>
    <id>https://gangmax.me/blog/2024/01/04/google-photos-is-stuck-on-preparing-backup/</id>
    <published>2024-01-04T10:31:16.000Z</published>
    <updated>2026-04-17T07:25:55.192Z</updated>
    
    <content type="html"><![CDATA[<p>Recently when I was using Google Photos it’s always stuck on “preparing Backup”. I did the following things and the backup started then. Not sure what step(s) is(are) the key to solve the issue, but record them here:</p><ol><li><p>Go to “Settings” to enable the “Background App Refreshing” for this app.</p></li><li><p>Open the app, check the photos which are NOT be uploaded, open some of them can tap upload manually.</p></li><li><p>Check the videos which are not uploaded and do the same thing as step 2.</p></li></ol><p>After that the app started the upload process. Some other tricks such as “enable cellular backup” mentioned <a href="https://support.google.com/photos/thread/657678/my-google-photos-is-stuck-on-preparing-backup">here</a> may also work.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Recently when I was using Google Photos it’s always stuck on “preparing Backup”. I did the following things and the backup started then. </summary>
      
    
    
    
    
    <category term="google" scheme="https://gangmax.me/tags/google/"/>
    
  </entry>
  
  <entry>
    <title>Xcode: Open files in a new tab with &quot;Open Quickly&quot;</title>
    <link href="https://gangmax.me/blog/2023/12/28/xcode-open-files-in-a-new-tab-with-open-quickly/"/>
    <id>https://gangmax.me/blog/2023/12/28/xcode-open-files-in-a-new-tab-with-open-quickly/</id>
    <published>2023-12-28T16:40:29.000Z</published>
    <updated>2026-04-17T07:25:55.192Z</updated>
    
    <content type="html"><![CDATA[<p>From <a href="https://www.reddit.com/r/iOSProgramming/comments/m0nt8x/xcode_tip_open_files_in_a_new_tab_with_open/">here</a>.</p><p>In Xcode, when you press “Shift + Command + O” to use the “Open Quickly” feature to open a file, by default the file will be opened in the existing tab instead of a new tab. However I want to change this behavior to open the file in a new tab. This can be done by the following configuration:</p><ol><li><p>Press “Command + ,” to open “Settings”. Go to the “Navigation” tab.</p></li><li><p>Update the “Optional Navigation” by setting its value as “Uses Tab”.</p></li><li><p>Done.</p></li></ol><p>Now when you press “Shift + Command + O” to use the “Open Quickly” feature to open a file, after searching and selecting the file, press “Option + Enter” to open it. Now the file will be opened in a new tab.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;From &lt;a href=&quot;https://www.reddit.com/r/iOSProgramming/comments/m0nt8x/xcode_tip_open_files_in_a_new_tab_with_open/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In X</summary>
      
    
    
    
    
    <category term="xcode" scheme="https://gangmax.me/tags/xcode/"/>
    
  </entry>
  
  <entry>
    <title>Export Ebooks from iBooks</title>
    <link href="https://gangmax.me/blog/2023/12/11/export-ebooks-from-ibooks/"/>
    <id>https://gangmax.me/blog/2023/12/11/export-ebooks-from-ibooks/</id>
    <published>2023-12-11T12:04:49.000Z</published>
    <updated>2026-04-17T07:25:55.191Z</updated>
    
    <content type="html"><![CDATA[<p>From <a href="https://discussions.apple.com/thread/8216965">here</a>. Here are instructions how to copy the ebooks in MacOS iBooks app to outside.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> ~/Library/Mobile\ Documents/iCloud\~com\~apple\~iBooks/Documents</span><br><span class="line"><span class="built_in">mkdir</span> ~/ibooks</span><br><span class="line"><span class="built_in">cp</span> -r * ~/ibooks</span><br><span class="line"><span class="built_in">cd</span> ~ibooks</span><br><span class="line">xattr -cr .</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;From &lt;a href=&quot;https://discussions.apple.com/thread/8216965&quot;&gt;here&lt;/a&gt;. Here are instructions how to copy the ebooks in MacOS iBooks app to</summary>
      
    
    
    
    
    <category term="mac" scheme="https://gangmax.me/tags/mac/"/>
    
    <category term="ibooks" scheme="https://gangmax.me/tags/ibooks/"/>
    
  </entry>
  
  <entry>
    <title>Remove Virtual Environment Created by &quot;pipenv&quot;</title>
    <link href="https://gangmax.me/blog/2023/12/11/remove-virtual-environment-created-by-pipenv/"/>
    <id>https://gangmax.me/blog/2023/12/11/remove-virtual-environment-created-by-pipenv/</id>
    <published>2023-12-11T11:16:42.000Z</published>
    <updated>2026-04-17T07:25:55.191Z</updated>
    
    <content type="html"><![CDATA[<p>From <a href="https://stackoverflow.com/questions/48256391/how-to-remove-a-virtualenv-created-by-pipenv-run">here</a>.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Method 1. </span></span><br><span class="line">~&gt; pipenv --venv</span><br><span class="line">/Users/your_user_name/.local/share/virtualenvs/model-N-S4uBGU</span><br><span class="line">~&gt; <span class="built_in">rm</span> -rf /Users/your_user_name/.local/share/virtualenvs/model-N-S4uBGU</span><br><span class="line"></span><br><span class="line"><span class="comment"># Method 2.</span></span><br><span class="line">pipenv --<span class="built_in">rm</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;From &lt;a href=&quot;https://stackoverflow.com/questions/48256391/how-to-remove-a-virtualenv-created-by-pipenv-run&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=</summary>
      
    
    
    
    
    <category term="python" scheme="https://gangmax.me/tags/python/"/>
    
    <category term="pipenv" scheme="https://gangmax.me/tags/pipenv/"/>
    
  </entry>
  
  <entry>
    <title>pip: Set Multiple Index URLs</title>
    <link href="https://gangmax.me/blog/2023/12/05/pip-set-multiple-index-urls/"/>
    <id>https://gangmax.me/blog/2023/12/05/pip-set-multiple-index-urls/</id>
    <published>2023-12-05T15:28:52.000Z</published>
    <updated>2026-04-17T07:25:55.191Z</updated>
    
    <content type="html"><![CDATA[<p>From <a href="https://stackoverflow.com/a/41926975/3115617">here</a> and <a href="https://pip.pypa.io/en/stable/topics/configuration/">here</a>.</p><p>Please following the following file content to set multiple index URLs in “~&#x2F;.pip&#x2F;pip.conf”.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">[list]</span><br><span class="line">format=columns</span><br><span class="line">[global]</span><br><span class="line">index-url = https://pypi.testsite.com/simple</span><br><span class="line">extra-index-url= https://pypi.org/simple</span><br><span class="line">                 https://artifacts.testsite.com/artifactory/api/pypi/pypi-repos/simple</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;From &lt;a href=&quot;https://stackoverflow.com/a/41926975/3115617&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://pip.pypa.io/en/stable/topics/configuration/&quot;&gt;he</summary>
      
    
    
    
    
    <category term="python" scheme="https://gangmax.me/tags/python/"/>
    
    <category term="pip" scheme="https://gangmax.me/tags/pip/"/>
    
  </entry>
  
  <entry>
    <title>Get Audio from Youtube</title>
    <link href="https://gangmax.me/blog/2023/10/27/get-audio-from-youtube/"/>
    <id>https://gangmax.me/blog/2023/10/27/get-audio-from-youtube/</id>
    <published>2023-10-27T11:55:14.000Z</published>
    <updated>2026-04-17T07:25:55.191Z</updated>
    
    <content type="html"><![CDATA[<p>You need “<a href="https://github.com/yt-dlp/yt-dlp-nightly-builds/releases">yt-dlp</a>“ and “<a href="https://www.ffmpeg.org/">ffmpeg</a>“ installed.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Download the target Youbube video.</span></span><br><span class="line">./yt-dlp_linux <span class="string">&#x27;https://www.youtube.com/watch?v=oRxgYC5zrV4&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Check the video info.</span></span><br><span class="line">ffprobe <span class="string">&#x27;Full Super Mario World 2： Yoshi&#x27;</span>\<span class="string">&#x27;&#x27;</span>s Island OST [oRxgYC5zrV4].mp4<span class="string">&#x27;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"># Extract the audio stream from the video to &quot;mp3&quot; file.</span></span><br><span class="line"><span class="string">ffmpeg -i &#x27;</span>Full Super Mario World 2： Yoshi<span class="string">&#x27;\&#x27;</span><span class="string">&#x27;s Island OST [oRxgYC5zrV4].mp4&#x27;</span> -vn -ac 2 <span class="string">&#x27;Full Super Mario World 2： Yoshi&#x27;</span>\<span class="string">&#x27;&#x27;</span>s Island OST [oRxgYC5zrV4].mp3<span class="string">&#x27;</span></span><br></pre></td></tr></table></figure><p>The “mp4 -&gt; mp3” converting tip comes from <a href="https://stackoverflow.com/a/66120164/3115617">here</a>.</p><p>The Youtube video in the command is “Super Mario World 2: Yoshi’s Island OST”.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">Super Mario World 2: Yoshi&#x27;s Island (1995) was developed and published by Nintendo.</span><br><span class="line"></span><br><span class="line">The soundtrack was composed by Koji Kondo(近藤浩治).</span><br><span class="line"></span><br><span class="line">Tracklist:</span><br><span class="line"></span><br><span class="line">1.  Nintendo Logo (0:00)</span><br><span class="line">2.  Story Organ (0:04)</span><br><span class="line">3.  Yoshi&#x27;s Island (2:47)</span><br><span class="line">4.  Yoshi&#x27;s Start Demo (4:59)</span><br><span class="line">5.  Practice Course (6:01)</span><br><span class="line">6.  Game Start (6:44)</span><br><span class="line">7.  The Flower Garden (6:49)</span><br><span class="line">8.  Goal &amp; Score (9:42)</span><br><span class="line">9.  Underground (10:32)</span><br><span class="line">10.  Castle &amp; Fortress (12:19)</span><br><span class="line">11.  Kamek&#x27;s Theme (14:27)</span><br><span class="line">12.  Mid-Boss (14:59)</span><br><span class="line">13.  Athletic (16:13)</span><br><span class="line">14.  Above Ground (18:24)</span><br><span class="line">15.  Player Down (19:55)</span><br><span class="line">16.  Game Over (20:01)</span><br><span class="line">17.  In Front of the Boss&#x27; Room (20:11)</span><br><span class="line">18.  Big Boss (21:10)</span><br><span class="line">19.  Big Boss Clear (23:11)</span><br><span class="line">20.  Map (23:20)</span><br><span class="line">21.  Bonus Game (25:35)</span><br><span class="line">22.  Powerful Baby (27:00)</span><br><span class="line">23.  Koopa (28:00)</span><br><span class="line">24.  Koopa Clear (30:45)</span><br><span class="line">25.  Luigi&#x27;s Rescue (30:56)</span><br><span class="line">26.  Ending (32:28)</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;You need “&lt;a href=&quot;https://github.com/yt-dlp/yt-dlp-nightly-builds/releases&quot;&gt;yt-dlp&lt;/a&gt;“ and “&lt;a href=&quot;https://www.ffmpeg.org/&quot;&gt;ffmpeg&lt;/a</summary>
      
    
    
    
    
    <category term="mp3" scheme="https://gangmax.me/tags/mp3/"/>
    
    <category term="ffmpeg" scheme="https://gangmax.me/tags/ffmpeg/"/>
    
  </entry>
  
  <entry>
    <title>Enable Hexo Mathematics Formula Rendering</title>
    <link href="https://gangmax.me/blog/2023/08/10/enable-hexo-mathematics-formula-rendering/"/>
    <id>https://gangmax.me/blog/2023/08/10/enable-hexo-mathematics-formula-rendering/</id>
    <published>2023-08-10T12:43:36.000Z</published>
    <updated>2026-04-17T07:25:55.191Z</updated>
    
    <content type="html"><![CDATA[<p>From <a href="https://github.com/next-theme/hexo-filter-mathjax">here</a>.</p><p>To make Hexo be able to render the math formula written in TeX/LaTeX notation, we need to add the “hexo-filter-mathjax” plugin.</p><p>First, update the “pacakge.json” file by adding the following line in the “dependencies” section:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"hexo-filter-mathjax"</span>: <span class="string">"^0.9.0"</span>,</span><br></pre></td></tr></table></figure><p>Then run “npm install” to install it.</p><p>Second, update the “_config.yaml” file to add the corresponding configuration:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">mathjax:</span><br><span class="line">  tags: none # or 'ams' or 'all'</span><br><span class="line">  single_dollars: true # enable single dollar signs as in-line math delimiters</span><br><span class="line">  cjk_width: 0.9 # relative CJK char width</span><br><span class="line">  normal_width: 0.6 # relative normal (monospace) width</span><br><span class="line">  append_css: true # add CSS to pages rendered by MathJax</span><br><span class="line">  every_page: false # if true, every page will be rendered by MathJax regardless the `mathjax` setting in Front-matter</span><br><span class="line">  packages: # extra packages to load</span><br><span class="line">  extension_options: {}</span><br></pre></td></tr></table></figure><p>The last step is to add the following “front-matter” line into the post which contains the math formula written in TeX/LaTeX notation:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mathjax: true</span><br></pre></td></tr></table></figure><p>After that in the post you can write the formula like below:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># https://en.wikibooks.org/wiki/LaTeX/Mathematics</span><br><span class="line">$x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}$</span><br></pre></td></tr></table></figure><p>The rendered result looks like:</p><p><mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.797ex;" xmlns="http://www.w3.org/2000/svg" width="14.815ex" height="3.262ex" role="img" focusable="false" viewBox="0 -1089.5 6548.3 1441.6"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D465" d="M52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289Z"></path></g><g data-mml-node="mo" transform="translate(849.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mfrac" transform="translate(1905.6,0)"><g data-mml-node="mrow" transform="translate(220,406.1) scale(0.707)"><g data-mml-node="mo"><path data-c="2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path></g><g data-mml-node="mi" transform="translate(778,0)"><path data-c="1D44F" d="M73 647Q73 657 77 670T89 683Q90 683 161 688T234 694Q246 694 246 685T212 542Q204 508 195 472T180 418L176 399Q176 396 182 402Q231 442 283 442Q345 442 383 396T422 280Q422 169 343 79T173 -11Q123 -11 82 27T40 150V159Q40 180 48 217T97 414Q147 611 147 623T109 637Q104 637 101 637H96Q86 637 83 637T76 640T73 647ZM336 325V331Q336 405 275 405Q258 405 240 397T207 376T181 352T163 330L157 322L136 236Q114 150 114 114Q114 66 138 42Q154 26 178 26Q211 26 245 58Q270 81 285 114T318 219Q336 291 336 325Z"></path></g><g data-mml-node="mo" transform="translate(1207,0)"><path data-c="B1" d="M56 320T56 333T70 353H369V502Q369 651 371 655Q376 666 388 666Q402 666 405 654T409 596V500V353H707Q722 345 722 333Q722 320 707 313H409V40H707Q722 32 722 20T707 0H70Q56 7 56 20T70 40H369V313H70Q56 320 56 333Z"></path></g><g data-mml-node="msqrt" transform="translate(1985,0)"><g transform="translate(853,0)"><g data-mml-node="msup"><g data-mml-node="mi"><path data-c="1D44F" d="M73 647Q73 657 77 670T89 683Q90 683 161 688T234 694Q246 694 246 685T212 542Q204 508 195 472T180 418L176 399Q176 396 182 402Q231 442 283 442Q345 442 383 396T422 280Q422 169 343 79T173 -11Q123 -11 82 27T40 150V159Q40 180 48 217T97 414Q147 611 147 623T109 637Q104 637 101 637H96Q86 637 83 637T76 640T73 647ZM336 325V331Q336 405 275 405Q258 405 240 397T207 376T181 352T163 330L157 322L136 236Q114 150 114 114Q114 66 138 42Q154 26 178 26Q211 26 245 58Q270 81 285 114T318 219Q336 291 336 325Z"></path></g><g data-mml-node="mn" transform="translate(462,289) scale(0.707)"><path data-c="32" d="M109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429Z"></path></g></g><g data-mml-node="mo" transform="translate(865.6,0)"><path data-c="2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path></g><g data-mml-node="mn" transform="translate(1643.6,0)"><path data-c="34" d="M462 0Q444 3 333 3Q217 3 199 0H190V46H221Q241 46 248 46T265 48T279 53T286 61Q287 63 287 115V165H28V211L179 442Q332 674 334 675Q336 677 355 677H373L379 671V211H471V165H379V114Q379 73 379 66T385 54Q393 47 442 46H471V0H462ZM293 211V545L74 212L183 211H293Z"></path></g><g data-mml-node="mi" transform="translate(2143.6,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(2672.6,0)"><path data-c="1D450" d="M34 159Q34 268 120 355T306 442Q362 442 394 418T427 355Q427 326 408 306T360 285Q341 285 330 295T319 325T330 359T352 380T366 386H367Q367 388 361 392T340 400T306 404Q276 404 249 390Q228 381 206 359Q162 315 142 235T121 119Q121 73 147 50Q169 26 205 26H209Q321 26 394 111Q403 121 406 121Q410 121 419 112T429 98T420 83T391 55T346 25T282 0T202 -11Q127 -11 81 37T34 159Z"></path></g></g><g data-mml-node="mo" transform="translate(0,88.9)"><path data-c="221A" d="M95 178Q89 178 81 186T72 200T103 230T169 280T207 309Q209 311 212 311H213Q219 311 227 294T281 177Q300 134 312 108L397 -77Q398 -77 501 136T707 565T814 786Q820 800 834 800Q841 800 846 794T853 782V776L620 293L385 -193Q381 -200 366 -200Q357 -200 354 -197Q352 -195 256 15L160 225L144 214Q129 202 113 190T95 178Z"></path></g><rect width="3105.6" height="42.4" x="853" y="846.5"></rect></g></g><g data-mml-node="mrow" transform="translate(1957.6,-345) scale(0.707)"><g data-mml-node="mn"><path data-c="32" d="M109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429Z"></path></g><g data-mml-node="mi" transform="translate(500,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g></g><rect width="4402.7" height="60" x="120" y="220"></rect></g></g></g></svg></mjx-container></p><p>Done.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;From &lt;a href=&quot;https://github.com/next-theme/hexo-filter-mathjax&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To make Hexo be able to render the math formula written</summary>
      
    
    
    
    
    <category term="hexo" scheme="https://gangmax.me/tags/hexo/"/>
    
    <category term="latex" scheme="https://gangmax.me/tags/latex/"/>
    
  </entry>
  
  <entry>
    <title>The DOF(Depth of field) formula</title>
    <link href="https://gangmax.me/blog/2023/08/09/the-dofdepth-of-field-formula/"/>
    <id>https://gangmax.me/blog/2023/08/09/the-dofdepth-of-field-formula/</id>
    <published>2023-08-09T12:58:18.000Z</published>
    <updated>2026-04-17T07:25:55.191Z</updated>
    
    <content type="html"><![CDATA[<p>The DOF(Depth of field) formula is:</p><p><mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -1.182ex;" xmlns="http://www.w3.org/2000/svg" width="14.357ex" height="3.407ex" role="img" focusable="false" viewBox="0 -983.7 6345.6 1506"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D437" d="M287 628Q287 635 230 637Q207 637 200 638T193 647Q193 655 197 667T204 682Q206 683 403 683Q570 682 590 682T630 676Q702 659 752 597T803 431Q803 275 696 151T444 3L430 1L236 0H125H72Q48 0 41 2T33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM703 469Q703 507 692 537T666 584T629 613T590 629T555 636Q553 636 541 636T512 636T479 637H436Q392 637 386 627Q384 623 313 339T242 52Q242 48 253 48T330 47Q335 47 349 47T373 46Q499 46 581 128Q617 164 640 212T683 339T703 469Z"></path></g><g data-mml-node="mi" transform="translate(828,0)"><path data-c="1D442" d="M740 435Q740 320 676 213T511 42T304 -22Q207 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435ZM637 476Q637 565 591 615T476 665Q396 665 322 605Q242 542 200 428T157 216Q157 126 200 73T314 19Q404 19 485 98T608 313Q637 408 637 476Z"></path></g><g data-mml-node="mi" transform="translate(1591,0)"><path data-c="1D439" d="M48 1Q31 1 31 11Q31 13 34 25Q38 41 42 43T65 46Q92 46 125 49Q139 52 144 61Q146 66 215 342T285 622Q285 629 281 629Q273 632 228 634H197Q191 640 191 642T193 659Q197 676 203 680H742Q749 676 749 669Q749 664 736 557T722 447Q720 440 702 440H690Q683 445 683 453Q683 454 686 477T689 530Q689 560 682 579T663 610T626 626T575 633T503 634H480Q398 633 393 631Q388 629 386 623Q385 622 352 492L320 363H375Q378 363 398 363T426 364T448 367T472 374T489 386Q502 398 511 419T524 457T529 475Q532 480 548 480H560Q567 475 567 470Q567 467 536 339T502 207Q500 200 482 200H470Q463 206 463 212Q463 215 468 234T473 274Q473 303 453 310T364 317H309L277 190Q245 66 245 60Q245 46 334 46H359Q365 40 365 39T363 19Q359 6 353 0H336Q295 2 185 2Q120 2 86 2T48 1Z"></path></g><g data-mml-node="mo" transform="translate(2617.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mfrac" transform="translate(3673.6,0)"><g data-mml-node="mrow" transform="translate(220,394) scale(0.707)"><g data-mml-node="mn"><path data-c="32" d="M109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429Z"></path></g><g data-mml-node="msup" transform="translate(500,0)"><g data-mml-node="mi"><path data-c="1D462" d="M21 287Q21 295 30 318T55 370T99 420T158 442Q204 442 227 417T250 358Q250 340 216 246T182 105Q182 62 196 45T238 27T291 44T328 78L339 95Q341 99 377 247Q407 367 413 387T427 416Q444 431 463 431Q480 431 488 421T496 402L420 84Q419 79 419 68Q419 43 426 35T447 26Q469 29 482 57T512 145Q514 153 532 153Q551 153 551 144Q550 139 549 130T540 98T523 55T498 17T462 -8Q454 -10 438 -10Q372 -10 347 46Q345 45 336 36T318 21T296 6T267 -6T233 -11Q189 -11 155 7Q103 38 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mn" transform="translate(605,363) scale(0.707)"><path data-c="32" d="M109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429Z"></path></g></g><g data-mml-node="mi" transform="translate(1508.6,0)"><path data-c="1D441" d="M234 637Q231 637 226 637Q201 637 196 638T191 649Q191 676 202 682Q204 683 299 683Q376 683 387 683T401 677Q612 181 616 168L670 381Q723 592 723 606Q723 633 659 637Q635 637 635 648Q635 650 637 660Q641 676 643 679T653 683Q656 683 684 682T767 680Q817 680 843 681T873 682Q888 682 888 672Q888 650 880 642Q878 637 858 637Q787 633 769 597L620 7Q618 0 599 0Q585 0 582 2Q579 5 453 305L326 604L261 344Q196 88 196 79Q201 46 268 46H278Q284 41 284 38T282 19Q278 6 272 0H259Q228 2 151 2Q123 2 100 2T63 2T46 1Q31 1 31 10Q31 14 34 26T39 40Q41 46 62 46Q130 49 150 85Q154 91 221 362L289 634Q287 635 234 637Z"></path></g><g data-mml-node="mi" transform="translate(2396.6,0)"><path data-c="1D436" d="M50 252Q50 367 117 473T286 641T490 704Q580 704 633 653Q642 643 648 636T656 626L657 623Q660 623 684 649Q691 655 699 663T715 679T725 690L740 705H746Q760 705 760 698Q760 694 728 561Q692 422 692 421Q690 416 687 415T669 413H653Q647 419 647 422Q647 423 648 429T650 449T651 481Q651 552 619 605T510 659Q484 659 454 652T382 628T299 572T226 479Q194 422 175 346T156 222Q156 108 232 58Q280 24 350 24Q441 24 512 92T606 240Q610 253 612 255T628 257Q648 257 648 248Q648 243 647 239Q618 132 523 55T319 -22Q206 -22 128 53T50 252Z"></path></g></g><g data-mml-node="msup" transform="translate(968.5,-377.4) scale(0.707)"><g data-mml-node="mi"><path data-c="1D453" d="M118 -162Q120 -162 124 -164T135 -167T147 -168Q160 -168 171 -155T187 -126Q197 -99 221 27T267 267T289 382V385H242Q195 385 192 387Q188 390 188 397L195 425Q197 430 203 430T250 431Q298 431 298 432Q298 434 307 482T319 540Q356 705 465 705Q502 703 526 683T550 630Q550 594 529 578T487 561Q443 561 443 603Q443 622 454 636T478 657L487 662Q471 668 457 668Q445 668 434 658T419 630Q412 601 403 552T387 469T380 433Q380 431 435 431Q480 431 487 430T498 424Q499 420 496 407T491 391Q489 386 482 386T428 385H372L349 263Q301 15 282 -47Q255 -132 212 -173Q175 -205 139 -205Q107 -205 81 -186T55 -132Q55 -95 76 -78T118 -61Q162 -61 162 -103Q162 -122 151 -136T127 -157L118 -162Z"></path></g><g data-mml-node="mn" transform="translate(636,289) scale(0.707)"><path data-c="32" d="M109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429Z"></path></g></g><rect width="2432" height="60" x="120" y="220"></rect></g></g></g></svg></mjx-container></p><ul><li><p>DOF = depth of field</p></li><li><p>u = distance to subject</p></li><li><p>N = f-Number</p></li><li><p>c = circle of confusion</p></li><li><p>f = focal length</p></li></ul><p>The formula comes from Google <a href="https://www.google.com/search?q=depth+of+field+formula&oq=depth+of+field+formula">result</a>.</p><p>The method how to write mathematical expressions in markdown format(GitHub style) comes from <a href="https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/writing-mathematical-expressions">here</a>.</p><p>The “c” values of different camera can be found from the web page <a href="https://www.dofmaster.com/dofjs.html">here</a> which provides an online DoF calculator implementation. Here are some examples:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.005"</span>&gt;</span>Nikon Coolpix S8000<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.005"</span>&gt;</span>Nikon Coolpix S8200<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.005"</span>&gt;</span>Nikon Coolpix S8100<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.005"</span>&gt;</span>Nikon Coolpix S9100<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.005"</span>&gt;</span>Nikon Coolpix S9300<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.011"</span>&gt;</span>Nikon 1 J1<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.011"</span>&gt;</span>Nikon 1 V1<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D1H<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D1X<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D2H<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D2Hs<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D2X<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D2Xs<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.030"</span>&gt;</span>Nikon D3<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.030"</span>&gt;</span>Nikon D3x<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.030"</span>&gt;</span>Nikon D3s<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.030"</span>&gt;</span>Nikon D4<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D40<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D40x<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D50<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D60<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D70<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D70s<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D80<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D90<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D100<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D200<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D300<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D300S<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.030"</span>&gt;</span>Nikon D700<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.030"</span>&gt;</span>Nikon D800<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.030"</span>&gt;</span>Nikon D800E<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D3000<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D3100<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D3200<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D5000<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D5100<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">"0.020"</span>&gt;</span>Nikon D7000<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br></pre></td></tr></table></figure><p>From the list you can see that, for a “full frame(35mm)” cemara the “c” value is “0.030” while for a “APS-C frame” camera the “c” value is “0.020”. These two values cover most of the user scenarios.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;The DOF(Depth of field) formula is:&lt;/p&gt;
&lt;p&gt;&lt;mjx-container class=&quot;MathJax&quot; jax=&quot;SVG&quot;&gt;&lt;svg style=&quot;vertical-align: -1.182ex;&quot; xmlns=&quot;http://</summary>
      
    
    
    
    
    <category term="photo" scheme="https://gangmax.me/tags/photo/"/>
    
  </entry>
  
  <entry>
    <title>OTF vs TTF</title>
    <link href="https://gangmax.me/blog/2023/07/11/otf-vs-ttf/"/>
    <id>https://gangmax.me/blog/2023/07/11/otf-vs-ttf/</id>
    <published>2023-07-11T10:21:55.000Z</published>
    <updated>2026-04-17T07:25:55.191Z</updated>
    
    <content type="html"><![CDATA[<p>From <a href="https://www.coreldraw.com/en/tips/otf-vs-ttf/">here</a>.</p><h3 id="What-is-TTF"><a href="#What-is-TTF" class="headerlink" title="What is TTF?"></a>What is TTF?</h3><p>TTF is the short form for TrueType font. This is a product of coordinated efforts between Apple and Microsoft in the 1980s. TTF font was created because both Apple and Microsoft had a common goal: create a font that was capable of working across Windows and Mac and could be deciphered by most printers by default.</p><p>TTF puts both screen and printer font data in the same file. This makeup simplified the installation of new fonts and acted as an early hybrid platform font format that was compatible with most users’ devices.</p><h3 id="What-is-OTF"><a href="#What-is-OTF" class="headerlink" title="What is OTF?"></a>What is OTF?</h3><p>OpenType font, abbreviated OTF was also a product of collaboration between Adobe and Microsoft. OTF was also a hybrid creation with display font and printer font data packaged in a single platform.</p><p>OTF was designed to enable more functions than TTF. An example being that the former has a format capable of storing as much as 65,000 characters. The ability to keep extra characters besides the usual ones (A-Z, 0-9, punctuations, and symbols) gave designers a platform to add more character to OTF, such as:</p><ol><li>Ligatures</li><li>Glyphs</li><li>Small caps</li><li>Alternate characters</li><li>Old-style figure</li></ol><p>When using TTF, these had to be added as extra fonts but using OTF, they are all kept in a single file as the default typeface and can be accessed easily to consumers. Using OTF is a better option when it comes to designing, and it is, therefore, the preferred font by professional designers and hobbyists.</p><h3 id="The-difference-between-OTF-and-TTF"><a href="#The-difference-between-OTF-and-TTF" class="headerlink" title="The difference between OTF and TTF"></a>The difference between OTF and TTF</h3><p>The most significant difference between OTF and TTF for both rookie and professional designers lies in the advanced typesetting programs. Another big difference is that OTF has provisions like glyphs and ligatures that present a designer with multiple options to use.</p><h3 id="Which-font-is-better-for-your-needs"><a href="#Which-font-is-better-for-your-needs" class="headerlink" title="Which font is better for your needs?"></a>Which font is better for your needs?</h3><p>These extra options are usually not used by non-designers. In simpler terms, OTF is the better of the two where designers are concerned due to the additional features. However, for regular daily computer use, the difference between OTF and TTF is not consequential.</p><p>When comparing OTF and TTF, the more robust of the two is the former due to its additional features. OTF is designed with more features to help designers and typesetters have the flexibility they need to make adjustments to a piece and make it look better. For non-designers who use computers for typical day-to-day tasks, the many features added to OTF don’t make a difference since they have no use.</p><p>If you have to choose between the two, OTF is the choice to make, but if you cannot access OTF, there is no harm in using TTF.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;From &lt;a href=&quot;https://www.coreldraw.com/en/tips/otf-vs-ttf/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;What-is-TTF&quot;&gt;&lt;a href=&quot;#What-is-TTF&quot; class=&quot;headerlink&quot;</summary>
      
    
    
    
    
    <category term="font" scheme="https://gangmax.me/tags/font/"/>
    
  </entry>
  
  <entry>
    <title>@: MacOS Extended File Attribute</title>
    <link href="https://gangmax.me/blog/2023/06/25/macos-extended-file-attribute/"/>
    <id>https://gangmax.me/blog/2023/06/25/macos-extended-file-attribute/</id>
    <published>2023-06-25T11:30:02.000Z</published>
    <updated>2026-04-17T07:25:55.191Z</updated>
    
    <content type="html"><![CDATA[<p>When I list the files in MacOS, it shows results like below:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">~/Library/Fonts&gt; <span class="built_in">ls</span> -la</span><br><span class="line">...</span><br><span class="line">-rw-r--r--@   1 maxhuang  staff    317628 Jan 17  2020 Hack-Bold.ttf</span><br><span class="line">-rw-r--r--@   1 maxhuang  staff    322288 Jan 17  2020 Hack-BoldItalic.ttf</span><br><span class="line">-rw-r--r--@   1 maxhuang  staff    316156 Jan 17  2020 Hack-Italic.ttf</span><br><span class="line">-rw-r--r--@   1 maxhuang  staff    309408 Jan 17  2020 Hack-Regular.ttf</span><br><span class="line">-rw-r--r--    1 maxhuang  staff  32683280 Nov 10  2021 SourceHanSans-VF.otf.ttc</span><br><span class="line">-rw-r--r--    1 maxhuang  staff  56785868 Nov 10  2021 SourceHanSerif-VF.otf.ttc</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>What does the “@” sign here mean? The following answer comes from the post <a href="http://xahlee.info/comp/OS_X_extended_attributes_xattr.html">here</a>.</p><p>That means the file has Extended file attributes. What is Extended Attribute?</p><ol><li><p>Standard Attributes. For example, creation date, modification date, permission.</p></li><li><p>Extended Attributes. Stores extra, customizable, small info. For example, author name, file character encoding, short comments, security status.</p></li><li><p>Resource Fork. Widely used before Mac OS X , can be considered as a more elaborate extended attribute system, and may also hold main data of the file. (See: <a href="http://xahlee.info/UnixResource_dir/macosx.html">Mac OS X Resource Fork Tips</a>)</p></li></ol><p>Some related commands are listed below:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># View Extended Attribute with ls.</span></span><br><span class="line"><span class="built_in">ls</span> -l -@ &lt;filename&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Show Attribute with xattr.</span></span><br><span class="line">xattr &lt;filename&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Delete All Extended Attribute.</span></span><br><span class="line">xattr -c &lt;filename&gt;</span><br></pre></td></tr></table></figure><p>What to Put in Extended Attribute? Extended Attributes are name&#x2F;value pairs. The names are Unicode characters of less than 128 bytes. (they are probably encoded in utf-8 or utf-16) The values can be text or binary, recommended to be less than 4 kibibit.</p><p>There’s no standardize structure for names. Anyone can add a attribute named for example “xyz” to a particular file. Apple recommends the use of reverse DNS naming scheme to prevent name clash. This is the same scheme adopted by Java. Example:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">com.apple.FinderInfo</span><br></pre></td></tr></table></figure><p>If you have a domain name, such as “example.com” then your attribute name for file of author can be “com.example.author”. If you don’t have a domain name, just make sure the name is not likely used by others.</p><p>In some case, if we want to remove all the extended attributes, such as removing the warnings that “the app is not from verified developer”, we can run the following command(from <a href="https://stackoverflow.com/a/58616002/3115617">here</a>):</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">xattr -cr &lt;file_or_dir&gt;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;When I list the files in MacOS, it shows results like below:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span </summary>
      
    
    
    
    
    <category term="mac" scheme="https://gangmax.me/tags/mac/"/>
    
    <category term="command" scheme="https://gangmax.me/tags/command/"/>
    
  </entry>
  
</feed>
