From ad146bfd7920addde25d3f1e40a94117236300ac Mon Sep 17 00:00:00 2001 From: hupeh Date: Tue, 7 Feb 2023 18:05:22 +0800 Subject: [PATCH] =?UTF-8?q?:tada:=20=E5=88=9D=E5=A7=8B=E5=8C=96=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + go.mod | 15 ++ go.sum | 36 +++++ main.go | 399 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 452 insertions(+) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..29b636a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +*.iml \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2fe0803 --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module git.yaojiankang.top/hupeh/gcz + +go 1.19 + +require github.com/AlecAivazis/survey/v2 v2.3.6 + +require ( + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/mattn/go-colorable v0.1.2 // indirect + github.com/mattn/go-isatty v0.0.8 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect + golang.org/x/term v0.0.0-20210503060354-a79de5458b56 // indirect + golang.org/x/text v0.3.3 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..6a6c818 --- /dev/null +++ b/go.sum @@ -0,0 +1,36 @@ +github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw= +github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= +github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= +github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w= +golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..7fd05cc --- /dev/null +++ b/main.go @@ -0,0 +1,399 @@ +package main + +import ( + "fmt" + "github.com/AlecAivazis/survey/v2" + "os" + "os/exec" + "strings" +) + +type Emoji string + +const ( + emojiTada = Emoji("🎉") // , "tada", "庆祝", "初次提交") + emojiNew = Emoji("🆕") // , "new", "全新", "引入新功能") + emojiBookmark = Emoji("🔖") // , "bookmark", "书签", "发行/版本标签") + emojiBug = Emoji("🐛") // , "bug", "bug", "修复 bug") + emojiAmbulance = Emoji("🚑") // , "ambulance", "急救车", "重要补丁") + emojiGlobeWithMeridians = Emoji("🌐") // , "globe_with_meridians", "地球", "国际化与本地化") + emojiLipstick = Emoji("💄") // , "lipstick", "口红", "更新 UI 和样式文件") + emojiClapper = Emoji("🎬") // , "clapper", "场记板", "更新演示/示例") + emojiRotatingLight = Emoji("🚨") // , "rotating_light", "警车灯", "移除 linter 警告") + emojiWrench = Emoji("🔧") // , "wrench", "扳手", "修改配置文件") + emojiHeavyPlusSign = Emoji("➕") // , "heavy_plus_sign", "加号", "增加一个依赖") + emojiHeavyMinusSign = Emoji("➖") // , "heavy_minus_sign", "减号", "减少一个依赖") + emojiArrowUp = Emoji("⬆️") // , "arrow_up", "上升箭头", "升级依赖") + emojiArrowDown = Emoji("⬇️") // , "arrow_down", "下降箭头", "降级依赖") + emojiZap = Emoji("⚡") // , "zap", "闪电", "提升性能") + emojiRacehorse = Emoji("🐎") // , "racehorse", "赛马", "提升性能") + emojiChartWithUpwardsTrend = Emoji("📈") // , "chart_with_upwards_trend", "上升趋势图", "添加分析或跟踪代码") + emojiRocket = Emoji("🚀") // , "rocket", "火箭", "部署功能") + emojiWhiteCheckMark = Emoji("✅") // , "white_check_mark", "白色复选框", "增加测试") + emojiMemo = Emoji("📝") // , "memo", "备忘录", "撰写文档") + emojiBook = Emoji("📖") // , "book", "书", "撰写文档") + emojiHammer = Emoji("🔨") // , "hammer", "锤子", "重大重构") + emojiArt = Emoji("🎨") // , "art", "调色板", "改进代码结构/代码格式") + emojiFire = Emoji("🔥") // , "fire", "火焰", "移除代码或文件") + emojiPencil2 = Emoji("✏️") // , "pencil2", "铅笔", "修复 typo") + emojiConstruction = Emoji("🚧") // , "construction", "施工", "工作进行中") + emojiWastebasket = Emoji("🗑️") // , "wastebasket", "垃圾桶", "废弃或删除") + emojiWheelchair = Emoji("♿") // , "wheelchair", "轮椅", "可访问性") + emojiConstructionWorker = Emoji("👷") // , "construction_worker", "工人", "添加 CI 构建系统") + emojiGreenHeart = Emoji("💚") // , "green_heart", "绿心", "修复 CI 构建问题") + emojiLock = Emoji("🔒") // , "lock", "锁", "修复安全问题") + emojiWhale = Emoji("🐳") // , "whale", "鲸鱼", "Docker 相关工作") + emojiApple = Emoji("🍎") // , "apple", "苹果", "修复在 MacOS 下的问题") + emojiGreenApple = Emoji("🍏") // , "green_apple", "IOS", "修复在 iOS 下的问题。") + emojiPenguin = Emoji("🐧") // , "penguin", "企鹅", "修复 Linux 下的问题") + emojiRobot = Emoji("🤖") // , "robot", "安卓", "修复 Android 下的问题") + emojiCheckeredFlag = Emoji("🏁") // , "checkered_flag", "旗帜", "修复 Windows 下的问题") + emojiTwistedRightwardsArrows = Emoji("🔀") // , "twisted_rightwards_arrows", "交叉箭头", "分支合并") + emojiSparkles = Emoji("✨") // , "sparkles", "新特性", "引入新特性") +) + +type emojiInfo struct { + flag, title, desc string +} + +var emojiInfos map[Emoji]*emojiInfo + +func (e Emoji) Flag() string { + if info, ok := emojiInfos[e]; ok { + return info.flag + } else { + return "" + } +} + +func (e Emoji) Title() string { + if info, ok := emojiInfos[e]; ok { + return info.title + } else { + return "" + } +} + +func (e Emoji) Description() string { + if info, ok := emojiInfos[e]; ok { + return info.desc + } else { + return "" + } +} + +type Type string + +const ( + typeFeat = Type("feat") // "新功能(feature)" + typeFix = Type("fix") // "自动修复问题 (适合于一次提交直接修复问题)" + typeTo = Type("to") // "不自动修复问题 (适合于多次提交,最终修复问题提交时使用fix)" + typeDocs = Type("docs") // "文档(documentation)" + typeStyle = Type("style") // "格式(不影响代码运行的变动)" + typeRefactor = Type("refactor") // "重构(即不是新增功能,也不是修改bug的代码变动)" + typePerf = Type("perf") // "优化相关,比如提升性能、体验" + typeTest = Type("test") // "增加测试" + typeChore = Type("chore") // "构建过程或辅助工具的变动" + typeRevert = Type("revert") // "回滚到上一个版本" + typeMerge = Type("merge") // "代码合并" +) + +var typeInfos map[Type]string + +func (e Type) Description() string { + if desc, ok := typeInfos[e]; ok { + return desc + } else { + return "" + } +} + +var emojisInType map[Type][]Emoji + +func init() { + typeInfos = map[Type]string{ + typeFeat: "新功能(feature)", + typeFix: "自动修复问题 (适合于一次提交直接修复问题)", + typeTo: "不自动修复问题 (适合于多次提交,最终修复问题提交时使用fix)", + typeDocs: "文档(documentation)", + typeStyle: "格式(不影响代码运行的变动)", + typeRefactor: "重构(即不是新增功能,也不是修改bug的代码变动)", + typePerf: "优化相关,比如提升性能、体验", + typeTest: "增加测试", + typeChore: "构建过程或辅助工具的变动", + typeRevert: "回滚到上一个版本", + typeMerge: "代码合并", + } + + emojiInfos = map[Emoji]*emojiInfo{ + emojiTada: {"tada", "庆祝", "初次提交"}, + emojiNew: {"new", "全新", "引入新功能"}, + emojiBookmark: {"bookmark", "书签", "发行/版本标签"}, + emojiBug: {"bug", "bug", "修复 bug"}, + emojiAmbulance: {"ambulance", "急救车", "重要补丁"}, + emojiGlobeWithMeridians: {"globe_with_meridians", "地球", "国际化与本地化"}, + emojiLipstick: {"lipstick", "口红", "更新 UI 和样式文件"}, + emojiClapper: {"clapper", "场记板", "更新演示/示例"}, + emojiRotatingLight: {"rotating_light", "警车灯", "移除 linter 警告"}, + emojiWrench: {"wrench", "扳手", "修改配置文件"}, + emojiHeavyPlusSign: {"heavy_plus_sign", "加号", "增加一个依赖"}, + emojiHeavyMinusSign: {"heavy_minus_sign", "减号", "减少一个依赖"}, + emojiArrowUp: {"arrow_up", "上升箭头", "升级依赖"}, + emojiArrowDown: {"arrow_down", "下降箭头", "降级依赖"}, + emojiZap: {"zap", "闪电", "提升性能"}, + emojiRacehorse: {"racehorse", "赛马", "提升性能"}, + emojiChartWithUpwardsTrend: {"chart_with_upwards_trend", "上升趋势图", "添加分析或跟踪代码"}, + emojiRocket: {"rocket", "火箭", "部署功能"}, + emojiWhiteCheckMark: {"white_check_mark", "白色复选框", "增加测试"}, + emojiMemo: {"memo", "备忘录", "撰写文档"}, + emojiBook: {"book", "书", "撰写文档"}, + emojiHammer: {"hammer", "锤子", "重大重构"}, + emojiArt: {"art", "调色板", "改进代码结构/代码格式"}, + emojiFire: {"fire", "火焰", "移除代码或文件"}, + emojiPencil2: {"pencil2", "铅笔", "修复 typo"}, + emojiConstruction: {"construction", "施工", "工作进行中"}, + emojiWastebasket: {"wastebasket", "垃圾桶", "废弃或删除"}, + emojiWheelchair: {"heelchair", "轮椅", "可访问性"}, + emojiConstructionWorker: {"construction_worker", "工人", "添加 CI 构建系统"}, + emojiGreenHeart: {"green_heart", "绿心", "修复 CI 构建问题"}, + emojiLock: {"lock", "锁", "修复安全问题"}, + emojiWhale: {"whale", "鲸鱼", "Docker 相关工作"}, + emojiApple: {"apple", "苹果", "修复在 MacOS 下的问题"}, + emojiGreenApple: {"green_apple", "IOS", "修复在 iOS 下的问题。"}, + emojiPenguin: {"penguin", "企鹅", "修复 Linux 下的问题"}, + emojiRobot: {"robot", "安卓", "修复 Android 下的问题"}, + emojiCheckeredFlag: {"checkered_flag", "旗帜", "修复 Windows 下的问题"}, + emojiTwistedRightwardsArrows: {"twisted_rightwards_arrows", "交叉箭头", "分支合并"}, + emojiSparkles: {"sparkles", "新特性", "引入新特性"}, + } + + var platformEmojis = []Emoji{ + emojiGreenHeart, + emojiGreenApple, + emojiLock, + emojiWhale, + emojiApple, + emojiPenguin, + emojiRobot, + emojiCheckeredFlag, + } + + platform := func(res ...Emoji) []Emoji { + return append(res, platformEmojis...) + } + + emojisInType = map[Type][]Emoji{ + typeFeat: {emojiNew, emojiSparkles}, + typeFix: platform(emojiBug, emojiAmbulance, emojiGlobeWithMeridians, emojiWrench, emojiPencil2), + typeTo: platform(emojiBug, emojiAmbulance, emojiGlobeWithMeridians, emojiWrench, emojiPencil2), + typeDocs: {emojiGlobeWithMeridians, emojiLipstick, emojiClapper, emojiWrench, emojiHeavyPlusSign, emojiHeavyMinusSign, emojiZap, emojiRacehorse, emojiMemo, emojiBook, emojiConstruction, emojiSparkles}, + typeStyle: platform(emojiTada, emojiGlobeWithMeridians, emojiLipstick, emojiClapper, emojiArt, emojiWheelchair), + typeRefactor: platform(emojiRotatingLight, emojiHeavyPlusSign, emojiHeavyMinusSign, emojiArt, emojiWastebasket, emojiConstructionWorker), + typePerf: platform(emojiGlobeWithMeridians, emojiLipstick, emojiArrowUp, emojiArrowDown, emojiZap, emojiRacehorse, emojiChartWithUpwardsTrend, emojiHammer, emojiConstructionWorker, emojiWheelchair), + typeTest: platform(emojiGlobeWithMeridians, emojiWrench, emojiHeavyPlusSign, emojiHeavyMinusSign, emojiArrowUp, emojiArrowDown, emojiWheelchair, emojiConstructionWorker), + typeChore: platform(emojiGlobeWithMeridians, emojiHeavyPlusSign, emojiHeavyMinusSign, emojiArrowUp, emojiArrowDown, emojiWheelchair), + typeRevert: platform(emojiBookmark, emojiRotatingLight), + typeMerge: {emojiBookmark, emojiTwistedRightwardsArrows}, + } +} + +type Message struct { + Type Type + Emoji Emoji + Scope string + Subject string +} + +func (m *Message) String() string { + var str string + if len(m.Type) > 0 { + str += string(m.Type) + if len(m.Scope) > 0 { + str += "(" + m.Scope + ")" + } else { + str += "(*)" + } + str += ": " + } + str += m.Subject + "\n\n" + return str +} + +func (m *Message) Preview() string { + var str string + for i, s := range strings.Split(m.String(), "\n") { + if i > 0 { + str += "\n" + } + str += "\t" + s + } + return str +} + +func (m *Message) Valid() bool { + return len(m.Type) > 0 && len(m.Subject) > 0 +} + +func (m *Message) AskType() error { + var types []Type + var options []string + for entry, info := range typeInfos { + types = append(types, entry) + options = append(options, string(entry)+" "+info) + } + qs := []*survey.Question{{ + Name: "value", + Prompt: &survey.Select{ + Message: "请选择提交类型:", + Options: options, + }, + }} + var res = struct { + Value int + }{} + err := survey.Ask(qs, &res, nil) + if err != nil { + return err + } + m.Type = types[res.Value] + return nil +} + +func (m *Message) AskScope() error { + qs := []*survey.Question{ + { + Name: "value", + Prompt: &survey.Input{Message: "影响范围:"}, + Transform: survey.ToLower, + }, + } + var res = struct { + Value string + }{} + err := survey.Ask(qs, &res, nil) + if err != nil { + return err + } + m.Scope = res.Value + return nil +} + +func (m *Message) AskEmoji() error { + if len(m.Type) == 0 { + err := m.AskType() + if err != nil { + return err + } + } + emojis, ok := emojisInType[m.Type] + if !ok || len(emojis) == 0 { + return nil + } + var es []Emoji + var opts []string + for _, e := range emojis { + es = append(es, e) + opts = append(opts, fmt.Sprintf("%s\x1b[36m (%s)\x1b[0m \x1b[2m- %s\x1b[0m", e, e.Title(), e.Description())) + } + // the questions to ask + var qs = []*survey.Question{{ + Name: "value", + Prompt: &survey.Select{ + Message: "设置 Emoji 符号:", + Options: opts, + }, + }} + var res = struct { + Value int + }{} + err := survey.Ask(qs, &res, nil) + if err != nil { + return err + } + m.Emoji = es[res.Value] + return nil +} + +func (m *Message) AskSubject() error { + if len(m.Type) == 0 { + err := m.AskType() + if err != nil { + return err + } + } + qs := []*survey.Question{{ + Name: "value", + Prompt: &survey.Input{Message: "日志内容,不超过50个字符"}, + }} + var res = struct { + Value string + }{} + err := survey.Ask(qs, &res, nil) + if err != nil { + return err + } + if len(res.Value) == 0 { + return m.AskSubject() + } + m.Subject = res.Value + return nil +} + +func (m *Message) Ask() error { + if err := m.AskType(); err != nil { + return err + } + if err := m.AskScope(); err != nil { + return err + } + if err := m.AskEmoji(); err != nil { + return err + } + if err := m.AskSubject(); err != nil { + return err + } + return nil +} + +func (m *Message) Confirm() (bool, error) { + for !m.Valid() { + err := m.Ask() + if err != nil { + return false, err + } + } + ok := false + prompt := &survey.Confirm{ + Message: "输入结果如下:\n\n" + m.Preview() + "\r立即提交?", + } + err := survey.AskOne(prompt, &ok) + if err != nil { + return false, err + } + if !ok { + return false, nil + } + return true, nil +} + +func (m *Message) Commit() error { + cmd := exec.Command("git", "commit", "-m", m.String()) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func main() { + var msg Message + ok, err := msg.Confirm() + if err == nil && ok { + err = msg.Commit() + } + if err != nil { + if err.Error() == "interrupt" { + fmt.Println("\nCtrl+C pressed in Terminal") + } else { + fmt.Println(err) + } + } +}