TypeScript 學習筆記(4) – 變數

JavaScript 提出了兩個新的關鍵字:let 與 const。使用 let 可以避免使用 var 的一些奇妙現象;而 const 就是跟 C 的 const 一樣,避免再次指定值給這個變數。

Variable Declarations 裡提了許多 JavaScript 使用 var 宣告變數時的奇妙現象,然後解說為什麼要改用 let。這部份我偷懶跳過去了,我的想法是,就都改用 let 吧。

另外有提到 Destructuring,就是類似 Python 的 unpack :

// JavaScript/TypeScript
let input = [1, 2];
let [first, second] = input;

Python 則是:

# Python
first, second = (1, 2)

也跟 Python 3 一樣,有取 remain items 的效果:

let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]

Object destructuring 是個比較特別的東西,意思跟 destructuring 差不多,但對象是 object,也就是可以將 object 的某幾個屬性放到變數裡。

let o = {
    a: "foo",
    b: 12,
    c: "bar"
}
let { a, b } = o;
({ a, b } = { a: "baz", b: 101 });  // 這樣也可

也支援 remaining items 的方法:

let { a, ...passthrough } = o;

Object destructuring 還支援 default values,意思是如果 wholeObject 沒有 b 屬性時,就把 b 指定為 1001:

function keepWholeObject(wholeObject: { a: string, b?: number }) {
    let { a, b = 1001 } = wholeObject;
}

也可以應用到 function 上,我覺得這個例子比較清楚,可以應用在只取物件的某幾個屬性來使用時:

function f({ a, b } = { a: "", b: 0 }): void {
    // ...
}
f(); // ok, default to { a: "", b: 0 }

Spread 算是陣列相加的一個 syntax sugar,可以把 first, second 塞到 bothPlus 這陣列裡去:

let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];

也可以應用在 object 上,目前想到可以用來做 object proxy 或是 composite 之類的,不過不適合也不一定。

let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { food: "rich", ...defaults };

 

TypeScript 學習筆記(3) – 基本型別

今天是看這篇:Basic Types

裏面直接建議用 ES6 的 let 替代 var 了。

  • boolean:布林型態
  • number:數字型態,沒有 integer, long, float, double 之類的,只有 number,JavaScript 裡的數字都是 float。
  • string:字串
  • array:陣列,在型別後面加上 [],例如: let list: number[] = [1, 2, 3]; ,要注意的是,無法指定固定個數。
  • tuple:就 tuple ,有點像陣列,但能指定每個元素的型態,這跟 Python 不一樣。例如:
    let x: [string, number];
    let x = ["Hello", 100];
    let y: [string, number] = ["John", 200];
    y[2] = "Doe";
    console.log(y);  // 輸出 [ 'John', 200, 'Doe' ]
    
  • enum:列舉,用法跟其他語言大致相似:
    enum Color {Red, Green, Blue};
    let c: Color = Color.Green;
    // 比較特別的用法
    let colorName: string = Color[2];
    console.log(colorName);  // 輸出 Blue
    
  • any:任意型態,但要注意的是跟其他語言所指的 Object 型別不同,TypeScript 有獨立一個 Object 型別。說起來跟 C# 的 dynamic 比較相似。
    let notSure: any = 4;
    notSure.ifItExists(); // 通過編譯,因為可能在執行時期就有 ifItExists()
    notSure.toFixed(); // 通過編譯,因為可能在執行時期就有 toFixed()
    
    let prettySure: Object = 4;
    prettySure.toFixed(); // 編譯錯誤,編譯時會有 Object 型別沒有 toFixed 的錯誤。
  • void:就 void ,表示不會傳回任何東西
  • null:就 null ,可以把任何型態的變數指定為 null
  • undefined:就 undefined,可以把任何型別的變數指定為 undefined
  • never:表示絕對不會傳回變數,
    // 只會丟出例外
    function error(message: string): never {
        throw new Error(message);
    }
    // 裏面是不會結束的迴圈
    function infiniteLoop(): never {
        while (true) {
        }
    }

Type assertion ,有 C/C# 強制轉型的意味。

let someValue: any = "this is a string";
let strLength: number = (someValue).length;
// 也可以用類似 c# as 的語法
let strLength: number2 = (someValue as string).length;

 

TypeScript 學習筆記(2) – Type annotation/Interface/Class

以下是看 Quick start 的紀錄:

  • Type annotation:一般動態語言是沒型別的,但 TypeScript 加上了這部份的支援,這可以讓開發者在編譯時期就預先發現型別錯誤,也可以編譯出更有效率的程式,讓程式執行的更快。語法像是 Go 或 Pascal (知道這語言的人應該不多了),是在變數後面加上 :type,例如:
    var str:string="hello world";
  • Interface:介面,沒啥特別的,看範例比較快
    interface Person {
        firstName: string;
        lastName: string;
    }
    
    function greeter(person: Person) {
        return "Hello, " + person.firstName + " " + person.lastName;
    }
    
    var user = { firstName: "Jane", lastName: "User" };
    
  • Class:大致上跟 java 的用法相似,不過在看到 QuickStart 範例時,還是驚訝了一下,主要是因為 TypeScript 語法並沒有明確指定類別實作了 Person 介面,但這個類別所產生的物件仍可以直接丟到只接介面的函式裡。後來仔細看了一下,才知道是因為 TypeScript 的 constructor (建構子)裡的參數加了 public,那麼會自動將這個參數放到同名的屬性去,也因此就符合了 Person 介面的要求。
    class Student {
        fullName: string;
        // 這裡的 public firstName,等同是函式裡有 this.firstName=firstName
        constructor(public firstName, public middleInitial, public lastName) {
            this.fullName = firstName + " " + middleInitial + " " + lastName;
        }
    }
    
    // 故意不要有 lastName
    class Employee {
        constructor(public firstName) {
        }
    }
    
    interface Person {
        firstName: string;
        lastName: string;
    }
    
    // 因為 Student 裡有 firstName, lastName 屬性,視同實作了 Person
    function greeter(person : Person) {
        return "Hello, " + person.firstName + " " + person.lastName;
    }
    
    var user = new Student("Jane", "M.", "User");
    var employee = new Employee("John");
    console.log(greeter(user));
    // console.log(greeter(employee));  // 加了這行,編譯會發生錯誤,告知 employee 不符合 Person 介面
    

TypeScript 學習筆記(1) – 安裝與執行

我習慣用的環境是 Ubuntu,目前用的是 16.04 Xenial。

第一步是安裝,安裝 nodejs 有幾種方法:

  1. nodejs.org 下載 tarball,手動安裝。
  2. 用 debian package 來安裝 (nodesource)
  3. 用 nvm 來安裝

之前有用過 nvm 了,這次我選擇用 debian package 來安裝 LTS 版的 nodejs。

curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install -y nodejs

安裝好,就可以利用 npm 來安裝 TypeScript 了。不過,因為我不想把 typescript 安裝在系統路徑 (一般是用 sudo npm install -g typescript),所以我多設定了 .npmrc 以及環境變數

prefix = /home/user/.local
root = /home/user/.local/lib/node_modules
binroot = /home/user/.local/bin
manroot = /home/user/.local/share/man

 

export LOCAL_PATH="$HOME/.local"
export MANPATH="$LOCAL_PATH/share/man:$MANPATH"
export NODE_PATH="$LOCAL_PATH/lib/node_modules:$NODE_PATH"
export PATH="$LOCAL_PATH/bin:$PATH"

 

在設定好以後,使用 npm install -g 時,會將這些套件安裝到自己的 $HOME/.local 目錄下。

在使用 npm install -g typescript 以後,我另外安裝了去年推出的 yarn。

curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt install yarn

 

首先寫一個 hello.ts

console.log("Hello world.");

然後用 tsc 來將 hello.ts 編譯為 hello.js: tsc hello.ts ,最後就可以用 node 執行 hello.js 印出 “Hello world.”

groovy 的編譯

用 Gradle 做很方便,build.gradle 裡要加

apply plugin: 'groovy'

然後程式碼依照 The Groovy Plugin – Gradle User Guide Version 3.2.1 裡的說明來安排就可以:

目錄 放什麼
src/main/java Java 程式碼 (對,可以跟 Java 一起編譯)
src/main/resources 會用到的資源
src/main/groovy Groovy 程式碼,也可以放 Java 程式碼
src/test/java Java 測試案例
src/test/resources 測試案例會用到的資源
src/test/groovy Groovy 測試案例
src/sourceSet/java Java source for the given source set
src/sourceSet/resources Resources for the given source set
src/sourceSet/groovy Groovy sources for the given source set. May also contain Java sources for joint compilation.

然後執行 gradle build 進行編譯,接著就可以在 build 裡找到 jar 檔案。

但是要注意的是,這個 jar 檔案不是 executable jar 檔案 (用 java -jar 就可執行的 jar ),要編譯為 executable jar 檔,得多做一些工。

第一個是要在 build.gradle 裡指定 manifest ,也就是在製作 jar 時,指定 META-INF ,告訴 java -jar 說,該執行哪個類別裡的 main。像下面就是自訂 Jar task ,並指定 manifest 的程式。

task uberjar(type: Jar) {
    from files(sourceSets.main.output.classesDir)
    from configurations.runtime.asFileTree.files.collect { zipTree(it) }

    manifest {
        attributes 'Main-Class': 'Program'  // 這邊就是依照 groovy 的類別與程式來放,假設下面的 groovy 程式放在 src/main/groovy/program.groovy
    }
}

接著,在 groovy 的主程式裡,加入 main ,這個 main 其實跟 java 的規定一樣,必須是 public, static ,接受字串陣列。


class Program {
  public static void main(String[] args){
    println "Hello world"
  }
}

最後執行 gradle uberjar ,就可以得到 executable jar 了
參考資料:

用指定的 SSH key 來操作 git

一般來說,都是直接使用 $HOME/.ssh 下這把預設產生的 id_rsa/id_rsa.pub 。那如果要使用另外的 SSH key 該怎麼辦呢?

這時候可以使用 GIT_SSH 這個環境變數搭配自訂的腳本來做:

  1. 先新增一個 custom_ssh.sh:
    #!/bin/sh
    exec /usr/bin/ssh -o StrictHostKeyChecking=no -i /home/me/my_private_key "$@"
    
  2. 在使用 git 的時候,提供 GIT_SSH 環境變數
    GIT_SSH="custom_ssh.sh" git clone your_repository

    ,這樣就可以了。

在什麼情況會用到額外的 SSH key 呢? 最常見的例子就是佈署:在 gitlab/github 裡可以有所謂的 deploy key,這樣在佈署的時候,就可以不提供自己的 SSH key 來做佈署,只透過這把 deploy key 來取得原始碼,增加安全性。

誰有用到 GIT_SSH ?

  1. Jenkins 裡可以新增 SSH key ,讓你在存取 repository 時,使用這把 SSH key。實際上 Jenkins 內部也是利用 GIT_SSH 的方法在運作,但並不是全部。
  2. Ansible 的 git module 也使用了 GIT_SSH 這個技巧 (應該是)。

也可以使用 ssh-add 搭配 ssh-agent 來替代 GIT_SSH 的作法,這樣就可以不需要額外新增一個自訂的腳本了。

產生獨立 SSH Key 的方法:

ssh-keygen -C your_comment -b 4096 -m pem -f key_filename -q -P "" -N "" 

執行上面的指令後,會產生兩個檔案,一個是公鑰,一個是私鑰。在要複製到別的地方使用時,要注意 permission 必須是 0600,僅允許擁有者存取才行。

參考自:Git clone with custom SSH using GIT_SSH error – Stack Overflow

awscwxls – 將 CloudWatch 數據匯出為 xls/xlsx

有人把程式寫好了,所以拿來用就可以:Exporting AWS CloudWatch Data | Pete Zybrick

  1. 確定有設定好 $HOME/.aws 目錄下的檔案。
  2. petezybrick/awscwxls: AWS Cloudwatch to Spreadsheet 上 git clone 下來
  3. 到 run 目錄下,先複製 properties/first.properties 為一個新的檔案,例如 properties/example.properties。然後修改內容,awscwxls 會讀取這個檔案的內容當作參數,像是要拿哪些數據、取哪些 instance、起始時間、區域等等的,都是在這邊設定。時間是用當地時間,程式會自動轉換為 UTC 時間 (AWS 上的時間)。
  4. 執行 ./runcwxls (Windows 下是 runcwxls.cmd) ,runcwxls 就會把取得的數據存到 xlsx 裡去了。

terraform + digitalocean

Terraform 對 DigitalOcean provider 的說明文件:

有例子當第一個雛型來抄抄改改還是比較快,所以主要參考資料裡的 example 來改 (要先 clone 下來),下面是我邊做邊記錄下來的步驟:

  1. 產生 ssh key:ssh-keygen -f your_key_file -t rsa
  2. 進 DigitalOcean 帳號裡,新增 ssh key,把剛剛產生的 public key 填入 (就 your_key_file.pub)  ,這邊主要參考 https://www.digitalocean.com/community/tutorials/how-to-use-ssh-keys-with-digitalocean-droplets
  3. 到 DigitalOcean 帳號的 API / Tokens 下,去新增 token ,並把 token 複製起來
  4. 用 curl -X GET -H “Content-Type: application/json” -H “Authorization: Bearer <your_token>” “https://api.digitalocean.com/v2/account/keys” 取得剛剛新增的 ssh key 的 key id  ,這個步驟主要參考 https://developers.digitalocean.com/documentation/v2/#list-all-keys
  5. 編輯 main.tf ,修改上個步驟取得的 ssh key id ,並把 connection 裡的 key file 改為步驟 1 所產生的 private key file 。
  6. export DIGITALOCEAN_TOKEN=”your_token”
  7. 執行 terraform plan / apply

參考資料:

Compile your go program inside the Docker container

golang 有官方製作的 container:https://hub.docker.com/_/golang/

用 docker pull golang 拉下來以後,切換到 go 專案目錄下 (假定是 $HOME/project),執行:

docker run --rm -v "$PWD":/usr/src/myapp -e GOBIN=/usr/src/myapp -w /usr/src/myapp golang:1.6 bash -c make

就可以在專案目錄下的 bin 裡找到 binary 了。

用 docker container 來 build 的好處,除了可以指定版本之外,也可以 cross compile ,另外就是可以省下處理佈署 golang 開發環境的心思。

docker-hackmd relative url

hackmd 是一個很棒的協作平台,你可以用 markdown 來撰寫文件,graphviz/flowchart 等語法來畫圖…很厲害。

安裝上也蠻簡單的,已經有人做好 Dockerfile :hackmdio/docker-hackmd: docker hackmd image

可是這個 docker image 有個問題,就是沒辦法以 relative url 存在,他預設是在根目錄下運作,有個日本人弄出來了:HackMDをnginxで / 以外のlocationで起動する。 – Qiita ,我參考他的設定,做了調整,加入 nginx 設定與 upstart 設定,放在 elleryq/docker-hackmd: docker hackmd image

大致調整以下東西:

  1. nginx 設定:加入 rewrite,將路徑改寫為 /hackmd,這可以參考 nginx.conf.example
  2. common.js:因為 hackmd 用到 websocket ,common.js 的 urlpath 也要跟著調整,否則會無法運作,裏面的 urlpath 需要修改為 /hackmd。這部份我寫在 hackmd/Dockerfile 裡,在用 git clone 取得 hackmd 原始碼以後,用 sed 去做字串的替換。
  3. upstart:upstart.hackmd.conf 裡是用 docker-compose 啟動 hackmd image ,這邊我預期 docker-compose.yml 是放在 /srv/docker-hackmd ,如果你預期不放在這兒,那麼這邊也要跟著調整。

應該大概就這些,如果有沒提到的,就看原始碼吧~