[Go 언어] Elm Archeitecture로 TUI 어플을 제작한 버블레테아로 조금 더 풍부한 ToDo 어플을 제작했습니다.
젓가락걸이
이 기사는 Elm Archeitecture like를 사용하여 TUI 애플리케이션bubbletea의 프레임워크로 ToDo 애플리케이션을 제작할 수 있다.
작업 목록, 추가, 편집, 완료 및 삭제를 표시할 수 있습니다.
이미지↓
Elm Archeitecture 정보
Elm Architecture를 모르시는 분들은 먼저 대충공식 가이드(일본어 번역)과이 보도를 읽어주시면 이해하기 쉽습니다.(이 편에는 Elm Architecture의 해설이 거의 없습니다.)
필자는 Elm을 조금 접했기 때문에 Elm Architecture에 대한 이해는 간단하다.
따라서 잘못이 있으면 댓글로 지적해 주세요.
본문에서 실현된 기능
또한 이 글은 퀘스트 일람표시와 퀘스트 추가 전의 해설(마법사)으로 활용됩니다.그 다음에 코드를 보충하는 일만 남았다.
코드를 읽고 분위기를 느끼고 싶으신 분, 다른 기능의 실현 방법을 상세히 알고 싶으신 분은 참조yuzuy/todo-cli하세요!
그럼 바로 실시합시다!
이루어지다
필자의 환경
릴리즈
OS
macOS Catalina 10.15.7
iTerm
3.3.12
Go
1.15.2
bubbletea
0.7.0
bubbles
0.7.0
주:bubbletea에 관해.이 프로그램을 설치하는 과정에서 파괴적인 변경이 발생했기 때문에 서로 다른 버전의 bubbletea를 사용할 때 오류가 발생한 경우 참조창고.하십시오.
작업 목록
이 섹션에는 작업 목록이 표시되고 커서로 작업을 선택할 수 있는 위치에 설치됩니다.
포장
go get
부터 할게요.// bubbletea
go get github.com/charmbracelet/bubbletea
// utility
go get github.com/charmbracelet/bubbles
Model
우선 임무의 구조를 정의한다.
main.go
type Task struct {
ID int
Name string
IsDone bool
CreatedAt time.Time
}
미니어처 구조라 다른 것FinishedAt
을 원하시면 추가해주세요.그리고 정의
model
.Elm Architecture에 해당하는 모델입니다.Elm Architecture에서 적용 상태를 모델로 관리합니다.
이 장에서
처리 상태는 작업 목록과 커서 위치 두 가지이기 때문에 설치는 다음과 같다.
main.go
type model struct {
cursor int
tasks []*Task
}
이 버블테아만 모델로 처리할 수 없습니다.모델 처리
model
로 하려면 tea.Model
를 실현해야 한다.tea.Model
의 정의↓// Model contains the program's state as well as it's core functions.
type Model interface {
// Init is the first function that will be called. It returns an optional
// initial command. To not perform an initial command return nil.
Init() Cmd
// Update is called when a message is received. Use it to inspect messages
// and, in response, update the model and/or send a command.
Update(Msg) (Model, Cmd)
// View renders the program's UI, which is just a string. The view is
// rendered after every Update.
View() string
}
우선 초기화 함수Init()
부터 실시하지만 이 앱은 가장 먼저 실행해야 할 명령이 없기 때문에nil에 답장하면 된다.(
model
struct의 초기화는 별도로 진행한다.)(이 글은 지령을 거의 사용하지 않기 때문에 지령에 별로 신경을 쓰지 않는다. 궁금하면 참조Elm 공식 가이드(일본어 번역)할 수 있다.)
main.go
import (
...
tea "github.com/charmbracelet/bubbletea"
)
...
func (m model) Init() tea.Cmd {
return nil
}
Update
Update()
에서 사용자의 조작(Msg)을 바탕으로 model
의 상태를 변경했다.main.go
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "j":
if m.cursor < len(m.tasks) {
m.cursor++
}
case "k":
if m.cursor > 1 {
m.cursor--
}
case "q":
return m, tea.Quit
}
}
return m, nil
}
tea.KeyMsg
처리(사용자의 키 조작)"j"를 누르면cursor의 값이 1 증가합니다.
"k"를 누르면cursor의 값이 1 감소합니다.
"q"를 누르면
tea.Quit
명령을 되돌려주고 프로그램을 종료합니다.커서가 무한히 위아래로 내려오면 귀찮아
m.cursor > 1
등의 조건이 있다.참고로
case "j", "down"
또는 case "k", "up"
라면 화살표 키로도 커서를 위아래로 이동할 수 있으니 취향에 따라 선택하세요.View
View()
에서 model
를 기반으로 한 텍스트를 생성합니다.string
고릴라가 썼다.main.go
func (m model) View() string {
s := "--YOUR TASKS--\n\n"
for i, v := range m.tasks {
cursor := " "
if i == m.cursor-1 {
cursor = ">"
}
timeLayout := "2006-01-02 15:04"
s += fmt.Sprintf("%s #%d %s (%s)\n", cursor, v.ID, v.Name, v.CreatedAt.Format(timeLayout))
}
s += "\nPress 'q' to quit\n"
return s
}
커서가 인덱스 번호에 따라 계수되지 않았기 때문에 인덱스i
와 m.cursor-1
를 비교하여 커서가 이 작업을 가리키는지 여부를 판단합니다.임무를 표시할 충분한 재료가 준비되어 있습니다!
main
정의 함수로 응용을 시작합시다!main
main
함수는 structmodel
를 초기화하고 프로그램을 시작합니다.main.go
func main() {
m := model{
cursor: 1,
tasks: []*Task{
{
ID: 1,
Name: "First task!",
CreatedAt: time.Now(),
},
{
ID: 2,
Name: "Write an article about bubbletea",
CreatedAt: time.Now(),
},
}
}
p := tea.NewProgram(m)
if err := p.Start(); err != nil {
fmt.Printf("app-name: %s", err.Error())
os.Exit(1)
}
}
원래 작업은 파일 등에서 읽지만 설치에 시간이 걸리기 때문에 이번에는 하드코딩을 할 것이다.tea.NewProgram()
에서 프로그램을 생성하고 p.Start()
에서 시작합니다.즉시
go run
명령으로 집행하세요!작업 일람표를 표시하고 "j", "k"키로 커서를 위아래로 이동합니다!
작업 추가
그러면 미션의 일람이 완성된 것으로 보이지만 이러면 ToDo 앱은 이름이 알려지지 않을 것 같습니다.
이 섹션에서는 ToDo 애플리케이션의 가장 중요한 기능 중 하나인 작업 추가를 구현합니다.
Model
이전에는 커서의 위치와 작업의 일람만 유지하면 됐지만 추가 작업을 수행할 때 사용자의 입력을 받아들여 유지할 필드를 설정해야 한다.
bubbletea에서 텍스트 입력을 실현하려면
github.com/charmbracelet/bubbles/textinput
라는 패키지를 사용하십시오.main.go
import (
...
input "github.com/charmbracelet/bubbles/textinput"
)
type model struct {
...
newTaskNameInput input.Model
}
이 항목만 작업 일람 표시 모드(이하 일반 모드)와 작업 추가 모드(이하 추가 모드라고 함)를 판별할 수 없기 때문에 이 필드도 추가mode
됩니다.main.go
type model struct {
mode int
...
newTaskNameInput input.Model
}
mode
의 식별자도 정의합니다.main.go
const (
normalMode = iota
additionalMode
)
Update
함수 변경에 바로 들어가고 싶지만, 추가 임무를 수행할 때 부족한 요소는 1개다.
이 ToDo 프로그램은 작업의 id를 순서대로 관리하기 때문에 최신 작업의 id를 미리 유지해야 합니다.
성명은 전역 변수로
Update()
초기화되고 main()
점차 증가한다.(제가 쓰는 과정에서 생각났어요.
Update()
의 필드로 관리할 수도 있어요.)중 선생은 그래도
model
의 장소를 선택하는 것이 좋겠다고 생각해서 거기에 설치했다.main.go
type model struct {
...
latestTaskID int
}
func main() {
...
// 今回はハードコーディング祭りですが、ちゃんと実装する場合はファイル等から読み込むときなどで初期値を入れましょう。
m.latestTaskID = 2
...
}
그러면 테마model
지만 추가 모드에서는 일반 모드와 달리 모든 문자 키가 입력으로 처리됩니다.따라서
Update()
와 달리 정의model.Update()
는 model.addingTaskUpdate()
라면 거기서 처리한다.정상 모드에서 "a"를 눌렀을 때 추가 모드로 변경하기 위해
추가 모드에서 "ctrl + q"를 눌렀을 때 정상 모드를 회복하기 위해 "enter"를 눌렀을 때 추가 작업을 추가합니다.
main.go
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.mode == additionalMode {
return m.addingTaskUpdate(msg)
}
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
...
case "a":
m.mode = additionalMode
...
}
return m, nil
}
func (m model) addingTaskUpdate(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+q":
m.mode = normalMode
m.newTaskNameInput.Reset()
return m, nil
case "enter":
m.latestTaskID++
m.tasks = append(m.tasks, &Task{
ID : m.latestTaskID,
Name: m.newTaskNameInput.Value(),
CreatedAt: time.Now(),
})
m.mode = normalMode
m.newTaskNameInput.Reset()
return m, nil
}
}
var cmd tea.Cmd
m.newTaskNameInput, cmd = input.Update(msg, m.newTaskNameInput)
return m, cmd
}
model.mode == additionalMode
에 입력한 값을 꺼내서 m.newTaskNameInput.Value()
로 초기화합니다.m.newTaskNameInput.Reset()
버튼 입력을 처리하고 업데이트input.Update()
.View
m.newTaskNameInput
도 View()
와 마찬가지로 분리 처리해야 한다.그런데 이거 6줄만 바꾸면 돼요!
main.go
func (m model) View() string {
if m.mode == additionalMode {
return m.addingTaskView()
}
...
}
func (m model) addingTaskView() string {
return fmt.Sprintf("Additional Mode\n\nInput a new task name\n\n%s", input.View(m.newTaskNameInput))
}
Update()
는 input.View()
를 input.Model
용View()
으로 바꾸는 변수다.main
새 필드
string
와 초기화mode
를 추가합니다.main.go
func main() {
newTaskNameInput := input.NewModel()
newTaskNameInput.Placeholder = "New task name..."
newTaskNameInput.Focus()
m := model{
mode: normalMode,
...
newTaskNameInput: newTaskNameInput,
}
...
}
newTaskNameInput
는 입력한 내용이 없을 때 표시되는 문자열입니다.호출
Placeholder
을 통해 이 Focus()
에 초점을 맞출 수 있습니다.이것은 한 화면에서 여러 개의 입력을 처리할 때 사용하는 것 같다.이번에는 두 개 이상 입력하라고 한 게 아니니까 괜찮다고 생각하면 괜찮아요.
그럼 임무의 추가도 이루어졌다!
당장 아까처럼
input.Model
명령으로 집행해 보세요!후기
이 기사는 버블테아를 활용한 다소 풍부한 ToDo 애플리케이션을 만드는 방법을 소개했다.
생각보다 간단히 구현하고, tig처럼 복잡한 CLI 도구를 만들어 보고 싶지만, 첫발을 내딛지 못한 저에게는 매력적인 프레임입니다.
Bubbletea는 이외에도 풍부한 TUI 응용 프로그램의 기능이 있으니 꼭 가보세요창고.!
Reference
이 문제에 관하여([Go 언어] Elm Archeitecture로 TUI 어플을 제작한 버블레테아로 조금 더 풍부한 ToDo 어플을 제작했습니다.), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/yuzuy/articles/95e522a39a5423f5bff4텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)