ほぼcal互換のカレンダーコマンド

投稿者: | 2016年2月15日

前回触れそびれているんですが、数値チェックを追加したcheckOptionクラスのプロパティ取得をメソッドをひとまとめにしました。基本的にこのクラスを使うときは全プロパティを一括取得するはずなので、各プロパティを取得するたびに引数チェック処理self.checkSwitch()を呼び出すのは冗長ですよね。。。

で、いよいよテストな訳ですがテストケースが増えすぎて結果がスクロールアウトしてしまうようになったので、最後に合否の数を表示することにしました。ここでもしNGケースがカウントされていればあらためて確認すればいいかなと。

#! /usr/bin/swift
import Foundation
var pass: Int = 0
var fail: Int = 0
let testArguments: [(args:[String], raws:Int, cols:Int, weekFlag:Bool,
        year:Int, month:Int, adjust:Int, error:Int32)] =
    [(["no-option"], 1, 1, false, 0, 0, 0, 0),
    (["current-year", "-y"], 4, 3, false, 0, 1, 0, 0),
    (["specified-year", "-y", "2016"], 4, 3, false, 2016, 1, 0, 0),
    (["error-specified-year-upper", "-y", "10000"], 1, 1, false, 0, 0, 0, 1),
    (["error-specified-year-lower", "-y", "100"], 1, 1, false, 0, 0, 0, 2),
    (["error-specified-year-irregular", "-y", "qwerty"], 1, 1, false, 0, 0, 0, 3),
    (["error-specified-year", "-y", "2016", "extra"], 1, 1, false, 0, 0, 0, 9),
    (["current-month", "-m"], 1, 1, false, 0, 0, 0, 0),
    (["specified-month", "-m", "12"], 1, 1, false, 0, 12, 0, 0),
    (["error-specified-month-upper", "-m", "13"], 1, 1, false, 0, 0, 0, 4),
    (["error-specified-month-lower", "-m", "0"], 1, 1, false, 0, 0, 0, 5),
    (["error-specified-month-irregular", "-m", "qwerty"], 1, 1, false, 0, 0, 0, 6),
    (["specified-month-and-year", "-m", "12", "2016"], 1, 1, false, 2016, 12, 0, 0),
    (["error-specified-month-and-year-upper", "-m", "12", "20016"], 1, 1, false, 0, 0, 0, 1),
    (["error-specified-month-and-year-lower", "-m", "12", "200"], 1, 1, false, 0, 0, 0, 2),
    (["error-specified-month-and-year-irregular", "-m", "12", "qwerty"], 1, 1, false, 0, 0, 0, 3),
    (["error-specified-month-upper-and-year", "-m", "13", "2016"], 1, 1, false, 0, 0, 0, 4),
    (["error-specified-month-lower-and-year", "-m", "0", "2016"], 1, 1, false, 0, 0, 0, 5),
    (["error-specified-month-irregular-and-year", "-m", "qwerty", "2016"], 1, 1, false, 0, 0, 0, 6),
    (["error-specified-month", "-m", "2", "2016", "extra"], 1, 1, false, 0, 0, 0, 9),
    (["current-month-and-befor-after", "-3"], 1, 3, false, 0, 0, -1, 0),
    (["specified-month-and-befor-after", "-3", "2"], 1, 3, false, 0, 2, -1, 0),
    (["error-specified-month-upper-and-befor-after", "-3", "22"], 1, 1, false, 0, 0, 0, 4),
    (["error-specified-month-lower-and-befor-after", "-3", "0"], 1, 1, false, 0, 0, 0, 5),
    (["error-specified-month-irregular-and-befor-after", "-3", "qwerty"], 1, 1, false, 0, 0, 0, 6),
    (["specified-year-month-and-befor-after", "-3", "2", "2016"], 1, 3, false, 2016, 2, -1, 0),
    (["error-month-and-befor-after-year-upper", "-3", "2", "12016"], 1, 1, false, 0, 0, 0, 1),
    (["error-month-and-befor-after-year-lower", "-3", "2", "12"], 1, 1, false, 0, 0, 0, 2),
    (["error-month-and-befor-after-year-irregular", "-3", "2", "20a12"], 1, 1, false, 0, 0, 0, 3),
    (["error-month-upper-and-befor-after", "-3", "52", "2012"], 1, 1, false, 0, 0, 0, 4),
    (["error-month-lower-and-befor-after", "-3", "0", "2012"], 1, 1, false, 0, 0, 0, 5),
    (["error-month-irregular-and-befor-after", "-3", "1.2", "2012"], 1, 1, false, 0, 0, 0, 6),
    (["error-month-and-befor-after", "-3", "2", "2016", "extra"], 1, 1, false, 0, 0, 0, 9),
    (["three-month-from-current", "+3"], 1, 3, false, 0, 0, 0, 0),
    (["specified-three-month-from-current", "+3", "5"], 1, 3, false, 0, 5, 0, 0),
    (["error-specified-three-month-upper-from-current", "+3", "50"], 1, 1, false, 0, 0, 0, 4),
    (["error-specified-three-month-lower-from-current", "+3", "0"], 1, 1, false, 0, 0, 0, 5),
    (["error-specified-three-month-irregular-from-current", "+3", "-8"], 1, 1, false, 0, 0, 0, 6),
    (["specified-year-three-month-from-current", "+3", "5", "2016"], 1, 3, false, 2016, 5, 0, 0),
    (["error-specified-year-upper-three-month-from-current", "+3", "5", "102016"], 1, 1, false, 0, 0, 0, 1),
    (["error-specified-year-lower-three-month-from-current", "+3", "5", "0"], 1, 1, false, 0, 0, 0, 2),
    (["error-specified-year-irregular-three-month-from-current", "+3", "5", "-2020"], 1, 1, false, 0, 0, 0, 3),
    (["error-specified-year-three-month-upper-from-current", "+3", "502", "2020"], 1, 1, false, 0, 0, 0, 4),
    (["error-specified-year-three-month-lower-from-current", "+3", "0", "2020"], 1, 1, false, 0, 0, 0, 5),
    (["error-specified-year-three-month-irregular-from-current", "+3", "10.3", "2020"], 1, 1, false, 0, 0, 0, 6),
    (["error-three-month-from-current", "+3", "5", "2016", "extra"], 1, 1, false, 0, 0, 0, 9),
    (["current-week", "-w"], 1, 1, true, 0, 0, 0, 0),
    (["error-current-week", "-w", "extra"], 1, 1, false, 0, 0, 0, 9),
    (["error", "-x"], 1, 1, false, 0, 0, 0, 9)]
for var i in 0..<testArguments.count {
    print("\(testArguments[i].args) ", terminator:"")
    var options = (raws:0, cols:0, weekFlag:false, year:0, month:0, adjust:0, error:Int32(0))
    options = checkOption(arguments: testArguments[i].args).getCalendarFormat()
    if testArguments[i].raws == options.raws &&
       testArguments[i].cols == options.cols &&
       testArguments[i].weekFlag == options.weekFlag &&
       testArguments[i].year == options.year &&
       testArguments[i].month == options.month &&
       testArguments[i].adjust == options.adjust &&
       testArguments[i].error == options.error {
        print("\u{001B}[0;32m==> OK\u{001B}[0;30m")
        pass+=1
    } else {
        print("\u{001B}[0;37;41m==> NG\u{001B}[0;30m")
        print("Raws:\(options.raws), ", terminator:"")
        print("Cols:\(options.cols), ", terminator:"")
        print("WeekFormat:\(options.weekFlag)")
        print("StartYear:\(options.year), ", terminator:"")
        print("StartMonth:\(options.month), ", terminator:"")
        print("AdjustMonth:\(options.adjust), ", terminator:"")
        print("OptionError:\(options.error)")
        fail+=1
    }
}
print("\u{001B}[0;32mPassed:\(pass)\u{001B}[0;30m, \u{001B}[0;31mFailed:\(fail)\u{001B}[0;30m")

テストコード実行結果

OKです。ではカレンダープログラムに組み込みましょう。新しいcheckOptionクラスは、オプション設定エラーの内容を細かく捉えることができるので、メインプログラム側もこれに対応しましょう。

9から12行目にエラー内容に応じたエラーメッセージを仕込んでおき、63から66行目でオプション解析結果に従ったメッセージを表示後 、エラーコードとともに終了処理をおこないます。もちろんエラーでなければ正常処理に移行してカレンダーを表示します。

#! /usr/bin/swift
import Foundation
let month_odr: [String] = ["January", "Febrary", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
let week_odr: [[String]] = [["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], ["日","月","火","水","木","金","土"]]
let esc = (fg_bk:"\u{001B}[0;30m", fg_mg:"\u{001B}[0;35m", bg_wt:"\u{001B}[1;47m", bg_gy:"\u{001B}[47m")
var curr = (year:0, month:0, day:0, week_m:0, week_y:0)
var d_flag = (raws:0, cols:0, week:false, year:0, month:0, adjust:0, error:Int32(0))
var je_flag: Int = 0
let errorMessage: [String] = ["Cannot calculate over A.D.9999.", "Cannot calculate under A.D.1800.",
                              "Specified year is not natural number.", "Cannot calculate over 12.",
                              "Cannot calculate under 1.", "Specified month is not natural number.",
                              "", "", "Specified option is illegal."]
// シェル変数名を指定すると値を返す関数
func getEnvironmentVar(name: String) -> String? {
	guard let rawValue = getenv(name) else { return nil }
	return String(utf8String: rawValue)
}
// Date型の引数を受け取り、DateComponents型を返す関数
func getCalComp(date: Date) -> DateComponents {
    let cal: NSCalendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian)!
    let components: DateComponents = cal.components([.year, .month, .day, .weekday,
                                                        .weekOfYear, .weekOfMonth], from:date)
    return components
}
// 一日始まりのDateComponents型の引数を受け取り、その月の配列を返す関数
func getCalendar(components: DateComponents) -> (cal_seq: [String], dayorder: Int, totaldays: Int) {
    var endofmonth: [Int] = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    var cal_seq: [String] = week_odr[je_flag]
    if (components.year! % 4) == 0 && (components.year! % 100) != 0 || (components.year! % 400) == 0 {
        endofmonth[2]+=1
    }
    for _ in 0..<42 {
        cal_seq.append("  ")
    }
    var datestring: String
    for var i in 1..<endofmonth[components.month!]+1 {
        if i < 10 {
            datestring = " \(i)"
        } else {
            datestring = "\(i)"
        }
        if components.year! == curr.year && components.month! == curr.month && i == curr.day {
            datestring = "\(esc.bg_gy)\(datestring)\(esc.bg_wt)"
        }
        cal_seq[ (6 + i) + (components.weekday! - 1) ] = datestring
    }
    var dayorder = curr.day
    var totaldays = 0
    for var i in 0..<curr.month {
        dayorder += endofmonth[i]
    }
    for var i in 1..<12+1 {
        totaldays += endofmonth[i]
    }
    return (cal_seq, dayorder, totaldays)
}
// 実質プログラムの始まり
if getEnvironmentVar(name:"LANG") == "ja_JP.UTF-8" {
	je_flag = 1
}
let arguments: [String] = CommandLine.arguments
d_flag = checkOption(arguments: arguments).getCalendarFormat()
if d_flag.error != 0 {
    print("xcal:\(errorMessage[d_flag.error - 1])\nusage:  xcal [-m month [year]]\n        xcal -3 [month [year]]\n        xcal +3 [month [year]]\n        xcal -y [year]\n        xcal -w")
    exit(d_flag.error)
}
let now: Date = Date()
var cal_comp: DateComponents = getCalComp(date:now)
curr = (cal_comp.year!, cal_comp.month!, cal_comp.day!, cal_comp.weekOfMonth!, cal_comp.weekOfYear!)
if d_flag.year != 0 {
    cal_comp.year = d_flag.year
}
if d_flag.month != 0 {
    cal_comp.month = d_flag.month
}
cal_comp.month! += d_flag.adjust
cal_comp.day = 1
var sequence: [String] = []
var header: String = ""
var header_y: String = ""
var header_m: [String] = ["", "", "", "", "", "", "", "", "", "", "", ""]
var footer: String = ""
var limit = d_flag.raws * d_flag.cols
for var i in 0..<limit {
    var calen: Date = Calendar.current.date(from:cal_comp)!
    cal_comp = getCalComp(date:calen)
    var thismonth = getCalendar(components:cal_comp)
    sequence += thismonth.cal_seq
    footer += "(\(thismonth.dayorder)/\(thismonth.totaldays))\n"
    if (d_flag.cols == 3 && i == 1) || (d_flag.cols == 1 && i == 0) {
        header_y += "\(cal_comp.year!)"
    }
    if je_flag == 1 {
        header_m[i] = "\(cal_comp.month!)月"
    } else {
        header_m[i] = "\(month_odr[cal_comp.month! - 1])"
    }
    cal_comp.month!+=1
}
var length: Int
if d_flag.cols == 3 {
    length = (65 - header_y.characters.count) / 2
    for var i in 0..<length+1 {
        header_y = " " + header_y
    }
    print("\(header_y)\n")
}
var hol: [Int] = [0, 6]
for var l in 0..<d_flag.raws {                          // 1または4連表示ループ
    if d_flag.cols == 3 {                               // 3連表示時の月ヘッダ作成
        for var i in 0..<d_flag.cols {
            length = (21 - header_m[i + 3 * l].characters.count - je_flag) / 2
            for var j in 0..<length {
                header_m[i + 3 * l] = " " + header_m[i + 3 * l]
            }
            length = 22 - header_m[i + 3 * l].characters.count - je_flag
            for var j in 0..<length {
                header_m[i + 3 * l] = header_m[i + 3 * l] + " "
            }
            header += header_m[i + 3 * l]
        }
    } else {                                            // 単月表示時の月ヘッダの作成
        header = "\(header_m[0]) \(header_y)"
        length = (21 - header.characters.count - je_flag) / 2
        for var j in 0..<length {
            header = " " + header
        }
    }
    print(header)
    header = ""
    for var i in 0..<7 {                                // 曜日ヘッダと6週表示
        if !(d_flag.week) || ((d_flag.week) && ((i == 0) || (i == curr.week_m))) {
            for var j in 0..<d_flag.cols {              // 3連表示時の週データの連結
                for var k in 0..<7 {                    // 週データの作成
                    for l in hol{
                        if l == k {
                            print(esc.fg_mg, terminator:"")
                        }
                    }
                    print("\(sequence[i * 7 + ((j + 3 * l) * 49 + k)])\(esc.fg_bk) ", terminator:"")
                }
                print(" ", terminator:"")
            }
            print("\n", terminator:"")
        }
    }
    if (d_flag.week) {
        print("US Week \(curr.week_y) \(footer)")
    }
}

これと前回作ったcheckOptionクラスを組み合わせればOK。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です