git config 的 includeIf

在推特上看到 @WanCW 的推文,才知道 git config 有 includeIf ,所以來研究一下怎麼用。
先看 git config 的說明:https://git-scm.com/docs/git-config ,用法蠻簡單的。

這邊先做假設情境

  • 使用者家目錄是 /home/user ,也就是 HOME=/home/user
  • $HOME/CompanyProjects 是公司專案目錄,而公司的 git server 是 gitlab.com
  • $HOME/SideProjects 是自己的專案目錄,自己的 git server 也是 gitlab.com
  • 公司用的 ssh key 在 $HOME/.ssh/company.pem
  • 自己用的 ssh key 在 $HOME/.ssh/id_rsa

為了避免使用者名稱、email 跟 SSH key 混用,這時候 git config 該怎麼設定呢?

首先在 $HOME 下建立 .gitconfig.d 目錄,在裡面放入 company.inc 與 side.inc,這兩個檔案的內容,在後面會提到。

先編輯 $HOME/.gitconfig 

; include if $GIT_DIR is under /home/user/CompanyProjects/
[includeIf “gitdir:/home/user/CompanyProjects/"]
    path = /home/user/.gitconfig.d/company.inc

; include if $GIT_DIR is under /home/user/SideProjects/
[includeIf “gitdir:/home/user/SideProjects/"]
    path = /home/user/.gitconfig.d/side.inc

再來是 company.inc 與 side.inc

; $HOME/.gitconfig.d/company.inc
[user]
    email = user@company.com
    name = user(company)

[core]
    sshcommand=ssh -i /home/user/.ssh/company.pem
; $HOME/.gitconfig.d/side.inc
[user]
    email = user@example.com
    name = user


在這樣設定之後,當切換到 $HOME/CompanyProjects 目錄下任一個專案目錄時,git 會使用 company.inc 裡的設定;切換到 $HOME/SideProjects 目錄下任一個專案目錄時,git 會使用 side.inc 裡的設定。

那也可以針對個別專案去設定,假設 $HOME/CompanyProjects/ProjectA 目錄會用到不一樣的,那就在 .gitconfig 加入

[includeIf “gitdir:~/CompanyProjects/ProjectA/“]
    path = ~/.gitconfig.d/projecta.inc

然後在 $HOME/.gitconfig.d 下新增 projecta.inc 即可。

typescript 的 dirname/basename

node 是有一個 path 的 package,但這個 package 會依賴 process 來取得平台環境,以決定要用 win32 或是 posix 來處理路徑。

但我其實只是要簡單的取得父路徑跟從路徑取出檔案或目錄名稱,不需要這樣麻煩。上網找到這篇:Javascript functions for basename and dirname 。它的實作是使用 regular expression 來處理,所以傳回的並不是單純的字串,而是一個陣列 RegExMatchArray 。參考 MDN 的 String.prototype.match() 來做調整,簡單的說,陣列的第一個元素就是處理的結果,所以只要先判斷傳回值是否為  null ,然後再傳回第一個元素即可。

// Typescript
/**
 * 取得父路徑
 */
export function dirname(path: string): string {
  let newPath = '';
  // 找符合 '/aaa/bbb/ccc/' 的字串
  const regExMatchArray = path.match(/.*\//);
  // 沒有找到,傳回 '/'
  if (regExMatchArray === null ) {
    newPath = '/';
  }
  else {
    // 有找到
    newPath = regExMatchArray[0];
    // 看最後字元是否為 '/',是的話,就移除。
    if (newPath.endsWith('/')) {
      newPath = newPath.substring(0, newPath.length - 1);
    }
    // 最後結果的長度為0,加上 '/'
    if (newPath.length === 0) {
      newPath = '/';
    }
  }
  return newPath;
}

/**
 * 取得檔名或目錄名
 */
export function basename(path: string): string {
  // 把 '/' (含)之前的字元都替換成空字串
  return path.replace(/.*\//, '');
}

Macbook 的 Touchbar 與 ESC

前幾天用 android studio 的 vim mode 打字打到一半,發現 Touchbar 上的 ESC 不見了,靠北,啊這樣是要怎麼打字打下去? 找了一下,在 macos – Esc button from touchbar has disappeared – Ask Different 找到解法,用

sudo pkill TouchBarServer

就可以找回失落的 ESC

在那個時間點,有上推特分享,推友跟我分享了不少方法:

  • 按 Ctrl + [ ,這個就等於是 ESC
  • 按 Command + ~
  • 用虛擬鍵盤
  • 按 Ctrl + C
  • 外接鍵盤
  • 把 jj 對應為 ESC

有點不好意思的是,這些我都還沒試過,先記下來,以免日後再次遇到。

gitlab 看不到 last commit 跟 last updated 問題

gitlab 升級到 13.0.5 以後,發現有這問題,原本以為是 gitlab-ce 問題,下次升級就解決了。但是今天升級到 13.0.6 以後,並沒有解決。 利用瀏覽器的開發者工具去查看,發現有個請求是回傳 404,仔細看他網址裡,有 %2F

https://host/user/gitlab-ce-commit-info-bug/-/refs/master/logs_tree/folder_1%2Ffolder_2?format=json&offset=0

手動把 %2F 改為 / 以後,就可以拿到資料。

利用 “gitlab-ce “refs/master” “%2F” 404gitlab-ce “refs/master” “%2F” 404” 這幾個關鍵字在 gitlab issue 209941 找到線索,是 Apache 問題。

Apache 要在 virtualhost 裡加上 AllowEncodedSlashes NoDecode

但我這樣試了以後,發現還是不行。後來看了 Apache 文件,知道除了 NoDecode 之外,還可以設定為 On:AllowEncodedSlashes On 

於是這樣就可以成功顯示了。

但是為了安全性問題 (Apache 文件不建議這樣設定),再去找了一下,看為什麼會這樣。
後來在 StackOverflow 上找到解答,除了要改 AllowEncodedSlashes 之外,還要改 mod_proxy ProxyPass ,在後面加上 nocanon 即可。

範例

AllowEncodedSlashes NoDecode
<Location /example/>
  ProxyPass http://anotherserver:8080/example/ nocanon
</Location>

iperf3 測試網路速度

閱讀 指令式的網路速度測試工具 iPerf3 ,揪出網路頻寬真實的一面 以後做的小摘錄。

簡單說用法,因為是要測試內部網路的網路速度,所以要兩台電腦。一台執行

iperf3 -s

一台執行

iperf3 -c <server_ip>

這樣就可以了。也有公用的伺服器可以測,這樣就要一台電腦就行 (用 ipef3 -c <server_ip>),亞洲區只有兩台,一台在中亞(哈薩克),一台在印尼,要注意,使用公用伺服器的話,測出來的速度並不是網路內部電腦間的速度,而是電腦到Router到外部伺服器的速度。

flutter stable/beta 並存

簡單說,有專案因為怕被影響到,只想用 stable 來建置,不想用 beta,可是又想要試驗 flutter web ,而 flutter web 又只有 beta channel 才有,所以想讓他們並存。

其實意外的簡單,就只是放在不同目錄,只要在使用的時候小心,不要用到不對的 flutter 就行,在 terminal 直接下指令的話,會需要特別注意。

# 切到你要放 flutter beta channel 的資料夾
git clone https://github.com/flutter/flutter.git flutter-beta -b beta
cd flutter-beta/bin
./flutter precache
./flutter config —enable-web
./flutter devices

在 Android Studio 建立 flutter project 時,會問 flutter SDK 路徑,把路徑指到剛剛放 flutter beta channel 的位置,就可以了。

Visual Studio Code 的話,需要改 workspace preferences ,改裡面的 dart.flutterSdkPath (來源1 / 來源2 )

"settings": {
"dart.flutterSdkPath": "/Users/youruser/flutter-beta"
}

直接用指令的話,就要確定 PATH 是對的,這時候可以預先寫好環境變數檔案,然後用 source 來處理

# flutter-stable.env
export PATH=/Users/youruser/flutter-stable:$PATH
# flutter-beta.env
export PATH=/Users/youruser/flutter-beta:$PATH
# 切換到 stable
source flutter-stable.env

# 切換到 beta
source flutter-beta.env

紀錄一下 MacOS 的中文輸入法

之前有找過一次了,今天又再找一次,這次把找的過程記錄下來。那這次為什麼要找呢?是因為小麥注音輸入法不知道為什麼當機終止了,選不到這個輸入法,想說找穩定的輸入法來使用。

之前換小麥注音輸入法的主因是因為我實在是無法適應 MacOS 的內建注音輸入法,用了一陣子還是無法適應,特別是打注音符號,就斷然放棄。

言歸正傳,目前可以找到的幾個:

  1. 小麥注音
  2. Yahoo!奇摩注音輸入法
  3. 超注音
  4. 香草輸入法 (沒有注音)
  5. 自然注音輸入法
  6. 鼠鬚管

小麥注音

用 homebrew 就可以安裝:brew install mcbopomofo

目前免費,沒有智慧選字,也沒有詞庫,按 shift+, 可以輸入全形標點符號。 網址:https://github.com/openvanilla/McBopomofo

Yahoo!奇摩注音輸入法

安裝需要手動,homebrew 以前有這個 cask,但是後來被移出去了。

目前免費,有智慧選字,輸入法程式沒有持續更新,Yahoo 已經將這整個輸入法開放原始碼了,安裝包是 zonble 大大熱心去編譯並打包的 (zonble大大就是OpenVilla香草輸入法/小麥注音輸入法的開發者)。 網址:https://github.com/zonble/ykk_installer/

按 shift+, 可以輸入全形標點符號

介紹:

超注音

安裝要繞個路,透過 Google Play,我猜想這是開發者要收費的緣故。價格:500 NTD

安裝方法:

  1. 使用 Android 手機,去 Google play 安裝 (超注音 for macOS)
  2. 開啟 app,裡面有操作說明,簡單的說,這包 apk 裡面有超注音的安裝程式,你要依照操作說明把這個安裝程式放到你的 macos 機器上,然後安裝。

網址:https://www.superkbd.com/

參考資料:超注音 for macOS 開放下載!為 Mac 再添一款輸入法

香草輸入法

安裝:brew install openvanilla

如果你需要倉頡、簡易(速成)、大易、行列、符號與日文假名、粵拼等輸入法,裝這個就對了,不過我沒用過。

網址:https://openvanilla.org/

自然注音輸入法

老牌的輸入法,要上官方網站購買,網址:https://www.goingpro.me/products

價格 2000 NTD,如果你有雙系統需求,可以考慮購買 Win+Mac 共通版,價格 3500 NTD,可以安裝在三台電腦上。

鼠鬚管

感謝推友 Ralphsun73221 的推薦。鼠鬚管就是只有注音的 RIME (Linux版是中州韻,Windows 版是小狼毫)。

安裝:brew cask install squirrel

介紹:【RIME 鼠鬚管注音版】Mac 中最好用的注音輸入法!

最終的選擇

之前是使用小麥注音輸入法,這個先保留,不移除。現在則是安裝了 Yahoo!奇摩注音輸入法來試用看看,未來如果不好用,再來考慮安裝鼠鬚管或是購買超注音或是購買自然注音輸入法。

剛剛使用,注意到一點,選字的游標位置不太一樣:

  • 小麥注音輸入法跟內建的注音輸入法,選字是選前個位置的字。
  • Windows/Linux 上的輸入法跟Yahoo!奇摩注音輸入法,選字是選後面位置的字。

筆記:Best practices for REST API design

來源:Best practices for REST API design

下面是看完以後,我的整理、摘要以及一點自己的想法:

  • 盡可能使用 JSON (application/json),除非有特殊需求,才來考慮使用其他格式,例如 form (上傳檔案) 或是 xml
  • 使用名詞,而非動詞:這樣才能搭配 HTTP verbs 的 POST/DELETE/GET/PUT,POST 放新資料到伺服器上,DELETE 移除資料,GET 取得資料,PUT 是更新資料
  • 時使用複數,表示取得一堆資料。
  • 階層物件可以使用巢狀資源,例如 /articles/article_id/comments/ 。P.S. 碰到這種情況其實也可以考慮使用 GraphQL
  • 漂亮的處理錯誤並回傳錯誤代碼,盡可能利用 HTTP 狀態碼而不要另外建立新的錯誤代碼。伺服器端在回傳錯誤時,可以帶上錯誤訊息,讓客戶端便於判斷、處理。
  • 允許篩選、排序跟分頁。篩選跟分頁可以避免一次拿過多資料,導致伺服器傳輸過多資料,也可以提升速度,拿的少,自然就快,對吧~
  • 安全性,SSL/TLS 是必要的,再來就是處理好使用者能存取的資源權限,這部份是實作 API 時必須要考慮到的。
  • 要用 cache,實作時要想,這資料會很常變動嗎?會很常被索取嗎?不常變動又很常被索取,就放到 cache 吧。
  • 幫 API 編上版號,這可以避免影響到舊有的 API,一方面也可以單純化,不用考慮到過多相容性的問題。例如:/v1, /v2

Angular i18n 的另外一個選擇 ngx-translate

Angular i18n 的官方作法並不讓人滿意,我後來找到 ngx-translate

先說明一下他的作法,他把這些翻譯好的字詞放到 JSON 翻譯檔去,在執行時就可以透過 http client 去拉取回來作動態的替換。

好處是,程式只要建置一次,不需要針對個別語言再次建置,網頁伺服器那邊也不需要特別寫設定去處理,而且可以做到動態切換語言。壞處是會有額外的 HTTP 請求,會增加流量。

ngx-translate 額外好的地方是,他提供了相應的工具、plugin,相當的方便。

那麼怎麼使用呢?我推薦看這篇【Angular】ngx-translate 多語系實務應用 ,他這篇的缺點是萃取字串的部分是手動,我建議萃取字串的部分可以用原作者 biesbjergngx-translate-extract  ,就不用自己找字串找的太累。

先進行安裝

npm install @ngx-translate/core —save
npm install @ngx-translate/http-loader --save

然後改 app.module.ts

import { TranslateModule, TranslateLoader, TranslateCompiler } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler';
import { HttpClientModule, HttpClient } from '@angular/common/http';

// … 省略 …

// 這主要是告訴 ngx-translate 翻譯檔該怎麼載入,用 TranslateHttpLoader 是表示以 HTTP 方式去下載、載入
export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}

// … 省略 …

// 設定
const translateConfig = {
  defaultLanguage: 'en-US',  // 預設是英文
  loader: {
    provide: TranslateLoader,
    useFactory: HttpLoaderFactory,  // 前面寫的 Factory
    deps: [HttpClient]
  },
  compiler: {
    provide: TranslateCompiler,
    useClass: TranslateMessageFormatCompiler
  }
};

@NgModule({
  // …
  imports: [
    BrowserAnimationsModule,
    BrowserModule,
    TranslateModule.forRoot(translateConfig),  // 模組帶設定
    // …
  ],
  // …
})

在 HTML 裡,使用

// 方法 1
{{ ‘your_translation_key’ | translate }}

// 方法 2
<div [translate]=“‘your_translation_key’”></div>

// 方法 3,適用於字串要用 HTML
<div [innerHTML]="'HELLO' | translate"></div>

他還可以帶參數,只是這邊我看不太懂,暫時也還用不到。

在程式裡,使用

// 先匯入
import {TranslateService} from '@ngx-translate/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';

// … 省略 …
export class AppComponent implements OnInit {
  l10n__title = '';

  constructor(public translate: TranslateService) {
  }

  ngOnInit(): void {
    const self = this;

    // 讓 ngx-translate-extract 可以抓到字串用的
    _('your_translation_key');

    // 取得字串都要使用 translate.get() 來預先取得
    this.translate.get('your_translation_key').subscribe((res: string) => {
      self.l10n__title = res;
    });
  }
}

這裡要特別說明一點,也是我當初用的時候搞錯的地方,就是上面用到的 your_translation_key 並不是字串,而是你自定義的代碼,裡面有使用 ‘.’ 的時候,在產出的 JSON 裡會變成 nested object ,舉個例子,假設這個 key 是 login.title,那麼 JSON 翻譯檔就會是

{
  "login": {
    "title": ""
  }
}

接下來講 ngx-translate-extract,ngx-translate-extract 的安裝

npm install @biesbjerg/ngx-translate-extract --save-dev

裝好以後,在 package.json 的 scripts 裡加入 (要產生什麼語言,請替換 {en,da,de,fi,nb,nl,sv} 這個字串,以繁體中文來說,是zh-TW或zh-Hant,只有英文跟繁體中文的話,就放 {en-US,zh-TW}。 )

// package.json
...
"scripts": {
  "i18n:init": "ngx-translate-extract --input ./src --output ./src/assets/i18n/template.json --key-as-default-value --replace --format json",
  "i18n:extract": "ngx-translate-extract --input ./src --output ./src/assets/i18n/{en,da,de,fi,nb,nl,sv}.json --clean --format json"
}
...

然後建立存放字串的資料夾:mkdir -p src/assets/i18n,執行 npm run i18n:extract 以後,就可以在 src/assets/i18n 裡看到翻譯檔了。

bfg

因為 git repository 裡有敏感資訊,為了安全起見,必須要把這些敏感資訊移掉。
找到這兩篇:

裡面大同小異,主要提到兩個方法,一個是 git filter-branch,一個是 bfg,這邊只介紹 bfg。 bfg 是一個相對於 git filter-branch 來說簡單、快速的工具,除了可以清理敏感資訊之外,也可以清掉超級大的檔案。

MacOS 的安裝可以用 brew:brew install bfg

使用步驟如下:

  1. 移除掉你要處理的檔案或是先清理掉你要處理的檔案內容,然後提交、推送到 repository 去。這段官方有特別說明 (在 Your current files are sacred… 這個小節),我是已經做完下面步驟,上 gitlab 網頁看檔案怎麼沒消失,花了點時間才在網頁上注意到這段,所以這個步驟要先作!!
  2. 切到另外一個目錄,例如 $HOME/tmp,對 repository 作 mirror,這樣拿下來的檔案結構不是一般的檔案佈局,而是 .git 下的佈局:git clone –mirror your_repository.git ,這時候 $HOME/tmp 下會有一個 your_repository.git
  3. 幾種使用方法:
    • 刪除檔案:bfg –delete-files your_file your_repository.git
    • 移除超過指定大小的檔案:bfg –strip-blobs-bigger-than 50M your_repository.git
    • 清理敏感資訊:先新增一個 passwords.txt,裡面放置要移除的文字,範例可以參考這個 gist ,然後執行 bfg –replace-text passwords.txt your_repository.txt
    • 刪除資料夾:bfg –delete-folders .git –delete-files .git –no-blob-protection your_repository.git
  4. 切到 your_repository.git 下,依照指示執行 git reflog expire –expire=now –all && git gc –prune=now –aggressive
  5. 推送回去:git push
  6. 大功告成