Stories

Detail Return Return

編程實踐|如何用 MoonBit 實現 diff(四) - Stories Detail

這是diff系列文章的第四部分。在前一篇中,我們深入討論了myers diff的線性空間優化版本。在本篇文章中,我們將在線性myers算法的基礎上構建一個完整的命令行程序,它可以輸出兩個文件的diff。

完整的代碼倉庫見此處: myers-diff, 可以在該倉庫根目錄下執行以下命令查看其效果:

moon update
moon build --target native
./target/native/release/build/main/main.exe tests/old1.txt tests/new1.txt

輸出如下:

--- tests/old1.txt
+++ tests/new1.txt
     1    1    aaaaaa
     2    2    bbb
     3    3    cccccccccc
+         4    dddddd

構建好的可執行文件位於target/native/release/build/main/main.exe, 你可以將它的名稱修改為diff.exe並放到某個PATH目錄下使用。

獲取命令行參數

目前moonbitlang/x庫已經提供了獲取命令行參數的API@moonbitlang/x/sys.get_cli_args,它的返回值是一個裝有所有命令行參數的數組,不過在不同後端下其行為會有些許變化,此處暫時選用native後端。在native後端下,命令行參數數組的第一個元素是MoonBit程序編譯出的可執行文件的路徑,後續元素都是用户自己傳遞的命令行參數。

一般來説,處理命令行參數用一些專門的解析庫會比較方便(MoonBit現在也有這樣的庫, 如Yoorkin/ArgParser),但對於我們這個比較簡單的diff程序,使用MoonBit最近新增的is運算符和guard語句結合就可以輕鬆處理了。

guard args is [executable, oldfile, newfile] else {
  println("wrong arguments: \{args}")
  println("Usage: \{args[0]} <file1> <file2>")
}

讀取文件

moonbitlang/x下的fs包提供了一些常見的文件IO函數,在這裏直接使用read_file_to_string即可,它的文件編碼參數encoding的默認值是utf8

read_file_to_string在讀取文件失敗的情況下(很常見的一種情況是參數裏的文件名不小心打錯了,或者沒有文件的讀權限)會拋出Error, 為簡化錯誤處理,此處直接將讀取失敗的文件名打印出來然後調用panic()

  let old = 
    try {
      (@diff.lines(@fs.read_file_to_string!(oldfile)))[:]
    } catch {
      _ => {
        println("\{executable}: failed to load file \{oldfile}")
        panic()
      }
    }
  let new = 
    try {
      (@diff.lines(@fs.read_file_to_string!(newfile)))[:]
    } catch {
      _ => {
        println("\{executable}: failed to load file \{newfile}")
        panic()
      }
    }

渲染

在終端中使用一些特定的控制字符可以讓輸出的文本帶有顏色,嘗試運行這段MoonBit代碼

test {
  println("\u001B[35m Hello MoonBit \u001B[39m")
}

它會輸出紫色的Hello MoonBit.

雖然不使用其他外部依賴也能達到輸出彩色文本的效果,但是手動拼接字符串比較乏味,而且很容易出錯,好在MoonBit社區已經有可用的終端渲染庫:Lampese/moonbit-chalk

在輸出diff時,常見的渲染選項是將插入渲染為綠色,刪除渲染為紅色,讓我們分別新建兩個名叫insdel的chalk對象,並分別設置顏色

  let ins = @chalk.chalk().color(Green)
  let del = @chalk.chalk().color(Red)

在輸出diff之前,最好也提醒一下用户所比較的是哪兩個文件,文件名為達到醒目的效果應該加粗輸出。

  let title = @chalk.chalk().modifier(Bold) // 加粗
  println("") // 留空一行
  println(title.render("--- \{oldfile}"))
  println(title.render("+++ \{newfile}"))

然後遍歷diff算法計算出的編輯序列並進行渲染

  for edit in result.iter() {
    match edit {
      Insert(_) => println(ins.render(@diff.pprint_edit(edit)))
      Delete(_) => println(del.render(@diff.pprint_edit(edit)))
      Equal(_) => println(@diff.pprint_edit(edit))
    }
  }
user avatar niandb Avatar shenshidedaxiongmao Avatar
Favorites 2 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.