package jrp
import (
"errors"
"github.com/yanosea/jrp/v2/pkg/proxy"
"github.com/yanosea/jrp/v2/pkg/utility"
)
// downloadUseCase is a struct that contains the use case of the download.
type downloadUseCase struct{}
// NewDownloadUseCase returns a new instance of the DownloadUseCase struct.
func NewDownloadUseCase() *downloadUseCase {
return &downloadUseCase{}
}
var (
// Du is a variable that contains the DownloadUtil struct for injecting dependencies in testing.
Du = utility.NewDownloadUtil(
proxy.NewHttp(),
)
// Fu is a variable that contains the FileUtil struct for injecting dependencies in testing.
Fu = utility.NewFileUtil(
proxy.NewGzip(),
proxy.NewIo(),
proxy.NewOs(),
)
)
// Run returns the output of the DownloadUseCase.
func (uc *downloadUseCase) Run(wnJpnDBPath string) error {
var deferErr error
if Fu.IsExist(wnJpnDBPath) {
return errors.New("wnjpn.db already exists")
}
resp, err := Du.Download(
"https://github.com/bond-lab/wnja/releases/download/v1.1/wnjpn.db.gz",
)
if err != nil {
return err
}
defer func() {
deferErr = resp.Close()
}()
tempFilePath, err := Fu.SaveToTempFile(resp.GetBody(), "wnjpn.db.gz")
if err != nil {
return err
}
defer func() {
deferErr = Fu.RemoveAll(tempFilePath)
}()
if err := Fu.ExtractGzFile(tempFilePath, wnJpnDBPath); err != nil {
return err
}
return deferErr
}
package jrp
import (
"context"
"errors"
historyDomain "github.com/yanosea/jrp/v2/app/domain/jrp/history"
)
// favoriteUseCase is a struct that contains the use case of the favoriting jrp from the table history in jrp sqlite database.
type favoriteUseCase struct {
historyRepo historyDomain.HistoryRepository
}
// NewFavoriteUseCase returns a new instance of the FavoriteUseCase struct.
func NewFavoriteUseCase(
historyRepo historyDomain.HistoryRepository,
) *favoriteUseCase {
return &favoriteUseCase{
historyRepo: historyRepo,
}
}
// Run returns the output of the AddFavorite usecase.
func (uc *favoriteUseCase) Run(ctx context.Context, ids []int, all bool) error {
var rowsAffected int
var err error
if all {
rowsAffected, err = uc.historyRepo.UpdateIsFavoritedByIsFavoritedIs(ctx, 1, 0)
} else {
rowsAffected, err = uc.historyRepo.UpdateIsFavoritedByIdIn(ctx, 1, ids)
}
if err != nil {
return err
}
if rowsAffected == 0 {
return errors.New("no histories to favorite")
}
return nil
}
package jrp
import (
"time"
"github.com/yanosea/jrp/v2/pkg/proxy"
"github.com/yanosea/jrp/v2/pkg/utility"
)
// generateJrpUseCase is a struct that contains the use case of the generation jrp.
type generateJrpUseCase struct{}
// NewGenerateJrpUseCase returns a new instance of the GenerateJrpUseCase struct.
func NewGenerateJrpUseCase() *generateJrpUseCase {
return &generateJrpUseCase{}
}
// GenerateJrpUseCaseInputDto is a DTO struct that contains the input data of the GenerateJrpUseCase.
type GenerateJrpUseCaseInputDto struct {
WordID int
Lang string
Lemma string
Pron string
Pos string
}
// GenerateJrpUseCaseOutputDto is a DTO struct that contains the output data of the GenerateJrpUseCase.
type GenerateJrpUseCaseOutputDto struct {
ID int
Phrase string
Prefix string
Suffix string
IsFavorited int
CreatedAt time.Time
UpdatedAt time.Time
}
var (
// ru is a variable that contains the RandUtil struct for injecting dependencies in testing.
ru = utility.NewRandUtil(proxy.NewRand())
)
// RunWithPrefix generates a jrp with the given prefix.
func (uc *generateJrpUseCase) RunWithPrefix(
dtos []*GenerateJrpUseCaseInputDto,
prefix string,
) *GenerateJrpUseCaseOutputDto {
if len(dtos) == 0 {
return nil
}
now := time.Now()
maxAttempts := len(dtos)
var jrp *GenerateJrpUseCaseOutputDto = nil
for i := 0; i < maxAttempts; i++ {
randomSuffix := dtos[ru.GenerateRandomNumber(len(dtos))]
if randomSuffix.Pos != "n" {
continue
}
jrp = &GenerateJrpUseCaseOutputDto{
ID: 0,
Phrase: prefix + randomSuffix.Lemma,
Prefix: prefix,
Suffix: "",
IsFavorited: 0,
CreatedAt: now,
UpdatedAt: now,
}
break
}
return jrp
}
// RunWithSuffix generates a jrp with the given suffix.
func (uc *generateJrpUseCase) RunWithSuffix(
dtos []*GenerateJrpUseCaseInputDto,
suffix string,
) *GenerateJrpUseCaseOutputDto {
if len(dtos) == 0 {
return nil
}
now := time.Now()
maxAttempts := len(dtos)
var jrp *GenerateJrpUseCaseOutputDto = nil
for i := 0; i < maxAttempts; i++ {
randomPrefix := dtos[ru.GenerateRandomNumber(len(dtos))]
if randomPrefix.Pos != "a" && randomPrefix.Pos != "v" {
continue
}
jrp = &GenerateJrpUseCaseOutputDto{
ID: 0,
Phrase: randomPrefix.Lemma + suffix,
Prefix: "",
Suffix: suffix,
IsFavorited: 0,
CreatedAt: now,
UpdatedAt: now,
}
break
}
return jrp
}
// RunWithRandom generates a jrp with random prefix and suffix.
func (uc *generateJrpUseCase) RunWithRandom(
dtos []*GenerateJrpUseCaseInputDto,
) *GenerateJrpUseCaseOutputDto {
if len(dtos) == 0 {
return nil
}
now := time.Now()
maxAttempts := len(dtos)
var jrp *GenerateJrpUseCaseOutputDto = nil
for i := 0; i < maxAttempts; i++ {
indexForPrefix := ru.GenerateRandomNumber(len(dtos))
indexForSuffix := ru.GenerateRandomNumber(len(dtos))
randomPrefix := dtos[indexForPrefix]
if randomPrefix.Pos != "a" && randomPrefix.Pos != "v" {
continue
}
randomSuffix := dtos[indexForSuffix]
if randomSuffix.Pos != "n" {
continue
}
jrp = &GenerateJrpUseCaseOutputDto{
ID: 0,
Phrase: randomPrefix.Lemma + randomSuffix.Lemma,
Prefix: "",
Suffix: "",
IsFavorited: 0,
CreatedAt: now,
UpdatedAt: now,
}
break
}
return jrp
}
package jrp
import (
"context"
"time"
historyDomain "github.com/yanosea/jrp/v2/app/domain/jrp/history"
)
// getHistoryUseCase is a struct that contains the use case of the getting jrp from the table history in jrp sqlite database.
type getHistoryUseCase struct {
historyRepo historyDomain.HistoryRepository
}
// NewGetHistoryUseCase returns a new instance of the GetHistoryUseCase struct.
func NewGetHistoryUseCase(
historyRepo historyDomain.HistoryRepository,
) *getHistoryUseCase {
return &getHistoryUseCase{
historyRepo: historyRepo,
}
}
// GetHistoryUseCaseOutputDto is a DTO struct that contains the output data of the GetHistoryUseCase.
type GetHistoryUseCaseOutputDto struct {
// ID is the identifier of the phrase.
ID int
// Phrase is the generated phrase.
Phrase string
// Prefix is the prefix when the phrase is generated.
Prefix string
// Suffix is the suffix when the phrase is generated.
Suffix string
// IsFavorited is the flag to indicate whether the phrase is favorited.
IsFavorited int
// CreatedAt is the timestamp when the phrase is created.
CreatedAt time.Time
// UpdatedAt is the timestamp when the phrase is updated.
UpdatedAt time.Time
}
// Run returns the output of the GetHistoryUseCase.
func (uc *getHistoryUseCase) Run(ctx context.Context, all bool, favorited bool, number int) ([]*GetHistoryUseCaseOutputDto, error) {
var histories []*historyDomain.History
var err error
if all && favorited {
histories, err = uc.historyRepo.FindByIsFavoritedIs(ctx, 1)
} else if all && !favorited {
histories, err = uc.historyRepo.FindAll(ctx)
} else if !all && favorited {
histories, err = uc.historyRepo.FindTopNByIsFavoritedIsAndByOrderByIdAsc(ctx, number, 1)
} else {
histories, err = uc.historyRepo.FindTopNByOrderByIdAsc(ctx, number)
}
if err != nil {
return nil, err
}
var ucDtos []*GetHistoryUseCaseOutputDto
for _, history := range histories {
ucDtos = append(ucDtos, &GetHistoryUseCaseOutputDto{
ID: history.ID,
Phrase: history.Phrase,
Prefix: history.Prefix.String,
Suffix: history.Suffix.String,
IsFavorited: history.IsFavorited,
CreatedAt: history.CreatedAt,
UpdatedAt: history.UpdatedAt,
})
}
return ucDtos, nil
}
package jrp
import ()
// getVersionUseCase is a struct that contains the use case of getting the version.
type getVersionUseCase struct{}
// NewGetVersionUseCase returns a new instance of the GetVersionUseCase struct.
func NewGetVersionUseCase() *getVersionUseCase {
return &getVersionUseCase{}
}
// GetVersionUseCaseOutputDto is a DTO struct that contains the output data of the GetVersionUseCase.
type GetVersionUseCaseOutputDto struct {
Version string
}
// Run returns the output of the GetVersionUseCase.
func (uc *getVersionUseCase) Run(version string) *GetVersionUseCaseOutputDto {
return &GetVersionUseCaseOutputDto{
Version: version,
}
}
package jrp
import (
"context"
"errors"
historyDomain "github.com/yanosea/jrp/v2/app/domain/jrp/history"
)
// removeHistoryUseCase is a struct that contains the use case of the removing jrp from the table history in jrp sqlite database.
type removeHistoryUseCase struct {
historyRepo historyDomain.HistoryRepository
}
// NewRemoveHistoryUseCase returns a new instance of the RemoveHistoryUseCase struct.
func NewRemoveHistoryUseCase(
historyRepo historyDomain.HistoryRepository,
) *removeHistoryUseCase {
return &removeHistoryUseCase{
historyRepo: historyRepo,
}
}
// Run returns the output of the RemoveHistoryUseCase.
func (uc *removeHistoryUseCase) Run(ctx context.Context, ids []int, all bool, force bool) error {
var rowsAffected int
var err error
if all && force {
rowsAffected, err = uc.historyRepo.DeleteAll(ctx)
} else if all && !force {
rowsAffected, err = uc.historyRepo.DeleteByIsFavoritedIs(ctx, 0)
} else if !all && force {
rowsAffected, err = uc.historyRepo.DeleteByIdIn(ctx, ids)
} else if !all && !force {
rowsAffected, err = uc.historyRepo.DeleteByIdInAndIsFavoritedIs(ctx, ids, 0)
}
if err != nil {
return err
}
if rowsAffected == 0 {
return errors.New("no histories to remove")
}
return nil
}
package jrp
import (
"context"
"time"
historyDomain "github.com/yanosea/jrp/v2/app/domain/jrp/history"
)
// saveHistoryUseCase is a struct that contains the use case of the saving jrp to the table history in jrp sqlite database.
type saveHistoryUseCase struct {
historyRepo historyDomain.HistoryRepository
}
// NewSaveHistoryUseCase returns a new instance of the SaveHistoryUseCase struct.
func NewSaveHistoryUseCase(
historyRepo historyDomain.HistoryRepository,
) *saveHistoryUseCase {
return &saveHistoryUseCase{
historyRepo: historyRepo,
}
}
// SaveHistoryUseCaseInputDto is a DTO struct that contains the output data of the SaveHistoryUseCase.
type SaveHistoryUseCaseInputDto struct {
// Phrase is the generated phrase.
Phrase string
// Prefix is the prefix when the phrase is generated.
Prefix string
// Suffix is the suffix when the phrase is generated.
Suffix string
// IsFavorited is the flag to indicate whether the phrase is favorited.
IsFavorited int
// CreatedAt is the timestamp when the phrase is created.
CreatedAt time.Time
// UpdatedAt is the timestamp when the phrase is updated.
UpdatedAt time.Time
}
// SaveHistoryUseCaseOutputDto is a DTO struct that contains the output data of the SaveHistoryUseCase.
type SaveHistoryUseCaseOutputDto struct {
// ID is the identifier of the phrase.
ID int
// Phrase is the generated phrase.
Phrase string
// Prefix is the prefix when the phrase is generated.
Prefix string
// Suffix is the suffix when the phrase is generated.
Suffix string
// IsFavorited is the flag to indicate whether the phrase is favorited.
IsFavorited int
// CreatedAt is the timestamp when the phrase is created.
CreatedAt time.Time
// UpdatedAt is the timestamp when the phrase is updated.
UpdatedAt time.Time
}
// Run returns the output of the SaveHistoryUseCase.
func (uc *saveHistoryUseCase) Run(ctx context.Context, inputDtos []*SaveHistoryUseCaseInputDto) ([]*SaveHistoryUseCaseOutputDto, error) {
var histories []*historyDomain.History
for _, dto := range inputDtos {
history := historyDomain.NewHistory(
dto.Phrase,
dto.Prefix,
dto.Suffix,
dto.IsFavorited,
dto.CreatedAt,
dto.UpdatedAt,
)
histories = append(histories, history)
}
histories, err := uc.historyRepo.SaveAll(ctx, histories)
if err != nil {
return nil, err
}
var outputDtos []*SaveHistoryUseCaseOutputDto
for _, history := range histories {
outputDto := &SaveHistoryUseCaseOutputDto{
ID: history.ID,
Phrase: history.Phrase,
Prefix: history.Prefix.String,
Suffix: history.Suffix.String,
IsFavorited: history.IsFavorited,
CreatedAt: history.CreatedAt,
UpdatedAt: history.UpdatedAt,
}
outputDtos = append(outputDtos, outputDto)
}
return outputDtos, nil
}
package jrp
import (
"context"
"time"
historyDomain "github.com/yanosea/jrp/v2/app/domain/jrp/history"
)
// searchHistoryUseCase is a struct that contains the use case of the searching jrp from the table history in jrp sqlite database.
type searchHistoryUseCase struct {
historyRepo historyDomain.HistoryRepository
}
// NewSearchHistoryUseCase returns a new instance of the SearchHistoryUseCase struct.
func NewSearchHistoryUseCase(
historyRepo historyDomain.HistoryRepository,
) *searchHistoryUseCase {
return &searchHistoryUseCase{
historyRepo: historyRepo,
}
}
// SearchHistoryUseCaseOutputDto is a DTO struct that contains the output data of the SearchHistoryUseCase.
type SearchHistoryUseCaseOutputDto struct {
// ID is the identifier of the phrase.
ID int
// Phrase is the generated phrase.
Phrase string
// Prefix is the prefix when the phrase is generated.
Prefix string
// Suffix is the suffix when the phrase is generated.
Suffix string
// IsFavorited is the flag to indicate whether the phrase is favorited.
IsFavorited int
// CreatedAt is the timestamp when the phrase is created.
CreatedAt time.Time
// UpdatedAt is the timestamp when the phrase is updated.
UpdatedAt time.Time
}
// Run returns the output of the SearchHistoryUseCase.
func (uc *searchHistoryUseCase) Run(ctx context.Context, keywords []string, and bool, all bool, favorited bool, number int) ([]*SearchHistoryUseCaseOutputDto, error) {
var histories []*historyDomain.History
var err error
if all && favorited {
histories, err = uc.historyRepo.FindByIsFavoritedIsAndPhraseContains(ctx, keywords, and, 1)
} else if all && !favorited {
histories, err = uc.historyRepo.FindByPhraseContains(ctx, keywords, and)
} else if !all && favorited {
histories, err = uc.historyRepo.FindTopNByIsFavoritedIsAndByPhraseContainsOrderByIdAsc(ctx, keywords, and, number, 1)
} else {
histories, err = uc.historyRepo.FindTopNByPhraseContainsOrderByIdAsc(ctx, keywords, and, number)
}
if err != nil {
return nil, err
}
var ucDtos []*SearchHistoryUseCaseOutputDto
for _, h := range histories {
ucDtos = append(ucDtos, &SearchHistoryUseCaseOutputDto{
ID: h.ID,
Phrase: h.Phrase,
Prefix: h.Prefix.String,
Suffix: h.Suffix.String,
IsFavorited: h.IsFavorited,
CreatedAt: h.CreatedAt,
UpdatedAt: h.UpdatedAt,
})
}
return ucDtos, nil
}
package jrp
import (
"context"
"errors"
historyDomain "github.com/yanosea/jrp/v2/app/domain/jrp/history"
)
// unfavoriteUseCase is a struct that contains the use case of the unfavoriting jrp from the table history in jrp sqlite database.
type unfavoriteUseCase struct {
historyRepo historyDomain.HistoryRepository
}
// NewUnfavoriteUseCase returns a new instance of the UnfavoriteUseCase struct.
func NewUnfavoriteUseCase(
historyRepo historyDomain.HistoryRepository,
) *unfavoriteUseCase {
return &unfavoriteUseCase{
historyRepo: historyRepo,
}
}
// Run returns the output of the Unfavorite usecase.
func (uc *unfavoriteUseCase) Run(ctx context.Context, ids []int, all bool) error {
var rowsAffected int
var err error
if all {
rowsAffected, err = uc.historyRepo.UpdateIsFavoritedByIsFavoritedIs(ctx, 0, 1)
} else {
rowsAffected, err = uc.historyRepo.UpdateIsFavoritedByIdIn(ctx, 0, ids)
}
if err != nil {
return err
}
if rowsAffected == 0 {
return errors.New("no favorited histories to unfavorite")
}
return nil
}
package wnjpn
import (
"context"
)
// FetchWordsUseCase is an interface that defines the use case of fetching words.
type FetchWordsUseCase interface {
Run(ctx context.Context, lang string, pos []string) ([]*FetchWordsUseCaseOutputDto, error)
}
// FetchWordsUseCaseStruct is a struct that implements the FetchWordsUseCase interface.
type FetchWordsUseCaseStruct struct {
wordQueryService WordQueryService
}
var (
// NewFetchWordsUseCase is a function that returns a new instance of the fetchWordsUseCase struct.
NewFetchWordsUseCase = newFetchWordsUseCase
)
// newFetchWordsUseCase returns a new instance of the fetchWordsUseCase struct.
func newFetchWordsUseCase(
wordQueryService WordQueryService,
) *FetchWordsUseCaseStruct {
return &FetchWordsUseCaseStruct{
wordQueryService: wordQueryService,
}
}
// FetchWordsUseCaseOutputDto is a DTO struct that contains the output data of the FetchWordsUseCase.
type FetchWordsUseCaseOutputDto struct {
WordID int
Lang string
Lemma string
Pron string
Pos string
}
// Run returns the output of the FetchWordsUseCase.
func (uc *FetchWordsUseCaseStruct) Run(ctx context.Context, lang string, pos []string) ([]*FetchWordsUseCaseOutputDto, error) {
qsDtos, err := uc.wordQueryService.FindByLangIsAndPosIn(ctx, lang, pos)
if err != nil {
return nil, err
}
var ucDtos []*FetchWordsUseCaseOutputDto
for _, qsDto := range qsDtos {
ucDtos = append(ucDtos, &FetchWordsUseCaseOutputDto{
WordID: qsDto.WordID,
Lang: qsDto.Lang.String,
Lemma: qsDto.Lemma.String,
Pron: qsDto.Pron.String,
Pos: qsDto.Pos.String,
})
}
return ucDtos, nil
}
package config
import (
"github.com/yanosea/jrp/v2/app/infrastructure/database"
"github.com/yanosea/jrp/v2/pkg/proxy"
"github.com/yanosea/jrp/v2/pkg/utility"
)
// Configurator is an interface that gets the configuration.
type Configurator interface {
GetConfig() (*JrpConfig, error)
}
// BaseConfigurator is a struct that implements the Configurator interface.
type BaseConfigurator struct {
Envconfig proxy.Envconfig
FileUtil utility.FileUtil
}
// JrpConfig is a struct that contains the configuration of the Jrp application.
type JrpConfig struct {
WNJpnDBType database.DBType
WNJpnDBDsn string
}
// GetConfig gets the configuration of the Jrp application.
func NewConfigurator(
envconfigProxy proxy.Envconfig,
fileUtil utility.FileUtil,
) *BaseConfigurator {
return &BaseConfigurator{
Envconfig: envconfigProxy,
FileUtil: fileUtil,
}
}
package history
import (
"database/sql"
"time"
)
// History is a struct that represents history table in the jrp database.
type History struct {
// ID is the primary key of the history table.
ID int
// Phrase is the generated phrase.
Phrase string
// Prefix is the prefix when the phrase is generated.
Prefix sql.NullString
// Suffix is the suffix when the phrase is generated.
Suffix sql.NullString
// IsFavorited is the flag to indicate whether the phrase is favorited.
IsFavorited int
// CreatedAt is the timestamp when the phrase is created.
CreatedAt time.Time
// UpdatedAt is the timestamp when the phrase is updated.
UpdatedAt time.Time
}
// NewHistory returns a new instance of the History struct.
func NewHistory(
phrase string,
prefix string,
suffix string,
isFavorited int,
createdAt time.Time,
updatedAt time.Time,
) *History {
return &History{
Phrase: phrase,
Prefix: sql.NullString{String: prefix, Valid: prefix != ""},
Suffix: sql.NullString{String: suffix, Valid: suffix != ""},
IsFavorited: isFavorited,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
}
package database
import (
"sync"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// DBConnection is an interface that contains the database connection.
type DBConnection interface {
Close() error
Open() (proxy.DB, error)
}
// dbConnection is a struct that contains the database connection.
type dbConnection struct {
sql proxy.Sql
db proxy.DB
driverName string
dataSourceName string
mutex *sync.RWMutex
}
// Close closes the database connection.
func (c *dbConnection) Close() error {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.db != nil {
err := c.db.Close()
if err != nil {
return err
}
c.db = nil
}
return nil
}
// Open opens the database connection.
func (c *dbConnection) Open() (proxy.DB, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.db != nil {
return c.db, nil
}
db, err := c.sql.Open(c.driverName, c.dataSourceName)
if err != nil {
return nil, err
}
c.db = db
return c.db, nil
}
package database
import (
"errors"
"sync"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
var (
// gcm is a global connection manager.
gcm ConnectionManager
// gmutex is a global mutex.
gmutex = &sync.Mutex{}
// GetConnectionManagerFunc is a function to get the connection manager.
GetConnectionManagerFunc = getConnectionManager
)
// ConnectionManager is an interface that manages database connections.
type ConnectionManager interface {
CloseAllConnections() error
CloseConnection(which DBName) error
GetConnection(which DBName) (DBConnection, error)
InitializeConnection(config ConnectionConfig) error
}
// connectionManager is a struct that implements the ConnectionManager interface.
type connectionManager struct {
sql proxy.Sql
connections map[DBName]DBConnection
mutex *sync.RWMutex
}
// NewConnectionManager initializes the connection manager.
func NewConnectionManager(sql proxy.Sql) ConnectionManager {
gmutex.Lock()
defer gmutex.Unlock()
if gcm == nil {
gcm = &connectionManager{
sql: sql,
connections: make(map[DBName]DBConnection),
mutex: &sync.RWMutex{},
}
}
return gcm
}
// GetConnectionManager gets the connection manager.
func GetConnectionManager() ConnectionManager {
return GetConnectionManagerFunc()
}
// getConnectionManager gets the connection manager.
func getConnectionManager() ConnectionManager {
gmutex.Lock()
defer gmutex.Unlock()
if gcm == nil {
return nil
}
return gcm
}
// ResetConnectionManager resets the connection manager.
func ResetConnectionManager() error {
gmutex.Lock()
defer gmutex.Unlock()
if gcm == nil {
return nil
}
if err := gcm.CloseAllConnections(); err != nil {
return err
} else {
gcm = nil
}
return nil
}
// CloseAllConnections closes all database connections.
func (cm *connectionManager) CloseAllConnections() error {
cm.mutex.Lock()
defer cm.mutex.Unlock()
for dbType, conn := range cm.connections {
if err := conn.Close(); err != nil {
return err
}
delete(cm.connections, dbType)
}
return nil
}
// CloseConnection closes the database connection.
func (cm *connectionManager) CloseConnection(dbType DBName) error {
cm.mutex.Lock()
defer cm.mutex.Unlock()
if conn, exists := cm.connections[dbType]; exists {
if err := conn.Close(); err != nil {
return err
}
delete(cm.connections, dbType)
}
return nil
}
// GetConnection gets the database connection.
func (cm *connectionManager) GetConnection(dbType DBName) (DBConnection, error) {
cm.mutex.RLock()
conn, exists := cm.connections[dbType]
cm.mutex.RUnlock()
if !exists {
return nil, errors.New("connection not initialized")
}
return conn, nil
}
// InitializeConnectionManager initializes the connection manager.
func (cm *connectionManager) InitializeConnection(config ConnectionConfig) error {
cm.mutex.Lock()
defer cm.mutex.Unlock()
if _, exists := cm.connections[config.DBName]; exists {
return errors.New("connection already initialized")
}
cm.connections[config.DBName] = &dbConnection{
sql: cm.sql,
db: nil,
driverName: string(config.DBType),
dataSourceName: config.DSN,
mutex: &sync.RWMutex{},
}
return nil
}
package repository
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/yanosea/jrp/v2/app/domain/jrp/history"
"github.com/yanosea/jrp/v2/app/infrastructure/database"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// HistoryRepository is a struct that implements the HistoryRepository interface.
type historyRepository struct {
connManager database.ConnectionManager
}
// NewHistoryRepository returns a new instance of the historyRepository struct.
func NewHistoryRepository() history.HistoryRepository {
return &historyRepository{
connManager: database.GetConnectionManager(),
}
}
// DeleteAll is a method that removes all the jrps from the history table.
func (h *historyRepository) DeleteAll(ctx context.Context) (int, error) {
var deferErr error
db, err := getJrpDB(ctx, h.connManager)
if err != nil {
return 0, err
}
tx, err := db.BeginTx(
ctx,
&sql.TxOptions{
Isolation: sql.LevelSerializable,
ReadOnly: false,
},
)
if err != nil {
return 0, err
}
defer func() {
deferErr = tx.Rollback()
}()
var result proxy.Result
if result, err = tx.ExecContext(ctx, DeleteAllQuery); err != nil {
return 0, err
}
if _, err := tx.ExecContext(ctx, DeleteSequenceQuery); err != nil {
return 0, err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return 0, err
}
if err := tx.Commit(); err != nil {
return 0, err
}
return int(rowsAffected), deferErr
}
// DeleteByIdIn is a method that removes the jrps from the history table by ID in.
func (h *historyRepository) DeleteByIdIn(ctx context.Context, ids []int) (int, error) {
var deferErr error
if len(ids) == 0 {
return 0, nil
}
db, err := getJrpDB(ctx, h.connManager)
if err != nil {
return 0, err
}
args := make([]interface{}, 0, len(ids))
for _, id := range ids {
args = append(args, id)
}
query := fmt.Sprintf(DeleteByIdInQuery, strings.Trim(strings.Repeat("?,", len(ids)), ","))
var result proxy.Result
if result, err = db.ExecContext(ctx, query, args...); err != nil {
return 0, err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return 0, err
}
return int(rowsAffected), deferErr
}
// DeleteByIdInAndIsFavoritedIs is a method that removes the jrps from the history table by ID in and is favorited is.
func (h *historyRepository) DeleteByIdInAndIsFavoritedIs(
ctx context.Context,
ids []int,
isFavorited int,
) (int, error) {
var deferErr error
if len(ids) == 0 {
return 0, nil
}
db, err := getJrpDB(ctx, h.connManager)
if err != nil {
return 0, err
}
placeholders := make([]string, len(ids))
for i := range placeholders {
placeholders[i] = "?"
}
query := fmt.Sprintf(DeleteByIdInAndIsFavoritedIsQuery, strings.Join(placeholders, ","))
args := make([]interface{}, len(ids)+1)
for i, id := range ids {
args[i] = id
}
args[len(ids)] = isFavorited
var result proxy.Result
if result, err = db.ExecContext(ctx, query, args...); err != nil {
return 0, err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return 0, err
}
return int(rowsAffected), deferErr
}
// DeleteByIsFavoritedIs is a method that removes the jrps from the history table by is favorited.
func (h *historyRepository) DeleteByIsFavoritedIs(ctx context.Context, isFavorited int) (int, error) {
var deferErr error
db, err := getJrpDB(ctx, h.connManager)
if err != nil {
return 0, err
}
var result proxy.Result
if result, err = db.ExecContext(ctx, DeleteByIsFavoritedIsQuery, isFavorited); err != nil {
return 0, err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return 0, err
}
return int(rowsAffected), deferErr
}
// FindAll is a method that finds all the jrps from the history table.
func (h *historyRepository) FindAll(ctx context.Context) ([]*history.History, error) {
var deferErr error
db, err := getJrpDB(ctx, h.connManager)
if err != nil {
return nil, err
}
rows, err := db.QueryContext(ctx, FindAllQuery)
if err != nil {
return nil, err
}
defer func() {
deferErr = rows.Close()
}()
histories := []*history.History{}
for rows.Next() {
history := &history.History{}
if err := rows.Scan(
&history.ID,
&history.Phrase,
&history.Prefix,
&history.Suffix,
&history.IsFavorited,
&history.CreatedAt,
&history.UpdatedAt,
); err != nil {
return nil, err
}
histories = append(histories, history)
}
return histories, deferErr
}
// FindByIsFavoritedIs is a method that finds the jrps from the history table by is favorited.
func (h *historyRepository) FindByIsFavoritedIs(ctx context.Context, isFavorited int) ([]*history.History, error) {
var deferErr error
db, err := getJrpDB(ctx, h.connManager)
if err != nil {
return nil, err
}
rows, err := db.QueryContext(ctx, FindByIsFavoritedIsQuery, isFavorited)
if err != nil {
return nil, err
}
defer func() {
deferErr = rows.Close()
}()
histories := []*history.History{}
for rows.Next() {
history := &history.History{}
if err := rows.Scan(
&history.ID,
&history.Phrase,
&history.Prefix,
&history.Suffix,
&history.IsFavorited,
&history.CreatedAt,
&history.UpdatedAt,
); err != nil {
return nil, err
}
histories = append(histories, history)
}
return histories, deferErr
}
// FindByIsFavoritedIsAndPhraseContains is a method that finds the jrps from the history table by is favorited and phrase contains.
func (h *historyRepository) FindByIsFavoritedIsAndPhraseContains(
ctx context.Context,
keywords []string,
and bool,
isFavorited int,
) ([]*history.History, error) {
var deferErr error
db, err := getJrpDB(ctx, h.connManager)
if err != nil {
return nil, err
}
args := make([]interface{}, 0, len(keywords)+1)
whereClause := ""
for i, keyword := range keywords {
if i == 0 {
whereClause += "Phrase LIKE ?"
} else {
if and {
whereClause += " AND Phrase LIKE ?"
} else {
whereClause += " OR Phrase LIKE ?"
}
}
args = append(args, "%"+keyword+"%")
}
args = append(args, isFavorited)
query := fmt.Sprintf(FindByIsFavoritedIsAndPhraseContainsQuery, whereClause)
rows, err := db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer func() {
deferErr = rows.Close()
}()
histories := []*history.History{}
for rows.Next() {
history := &history.History{}
if err := rows.Scan(
&history.ID,
&history.Phrase,
&history.Prefix,
&history.Suffix,
&history.IsFavorited,
&history.CreatedAt,
&history.UpdatedAt,
); err != nil {
return nil, err
}
histories = append(histories, history)
}
return histories, deferErr
}
// FindByPhraseContains is a method that finds the jrps from the history table by phrase contains.
func (h *historyRepository) FindByPhraseContains(
ctx context.Context,
keywords []string,
and bool,
) ([]*history.History, error) {
var deferErr error
db, err := getJrpDB(ctx, h.connManager)
if err != nil {
return nil, err
}
args := make([]interface{}, 0, len(keywords))
whereClause := ""
for i, keyword := range keywords {
if i == 0 {
whereClause += "Phrase LIKE ?"
} else {
if and {
whereClause += " AND Phrase LIKE ?"
} else {
whereClause += " OR Phrase LIKE ?"
}
}
args = append(args, "%"+keyword+"%")
}
query := fmt.Sprintf(FindByPhraseContainsQuery, whereClause)
rows, err := db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer func() {
deferErr = rows.Close()
}()
histories := []*history.History{}
for rows.Next() {
history := &history.History{}
if err := rows.Scan(
&history.ID,
&history.Phrase,
&history.Prefix,
&history.Suffix,
&history.IsFavorited,
&history.CreatedAt,
&history.UpdatedAt,
); err != nil {
return nil, err
}
histories = append(histories, history)
}
return histories, deferErr
}
// FindTopNByIsFavoritedIsAndByOrderByIdAsc is a method that finds the top N jrps from the history table by is favorited order by ID ascending.
func (h *historyRepository) FindTopNByIsFavoritedIsAndByOrderByIdAsc(
ctx context.Context,
number int,
isFavorited int,
) ([]*history.History, error) {
var deferErr error
db, err := getJrpDB(ctx, h.connManager)
if err != nil {
return nil, err
}
rows, err := db.QueryContext(ctx, FindTopNByIsFavoritedIsAndByOrderByIdAscQuery, isFavorited, number)
if err != nil {
return nil, err
}
defer func() {
deferErr = rows.Close()
}()
histories := []*history.History{}
for rows.Next() {
history := &history.History{}
if err := rows.Scan(
&history.ID,
&history.Phrase,
&history.Prefix,
&history.Suffix,
&history.IsFavorited,
&history.CreatedAt,
&history.UpdatedAt,
); err != nil {
return nil, err
}
histories = append(histories, history)
}
return histories, deferErr
}
// FindTopNByIsFavoritedIsAndByPhraseContainsOrderByIdAsc is a method that finds the top N jrps from the history table by is favorited and phrase contains order by ID ascending.
func (h *historyRepository) FindTopNByIsFavoritedIsAndByPhraseContainsOrderByIdAsc(
ctx context.Context,
keywords []string,
and bool,
number int,
isFavorited int,
) ([]*history.History, error) {
var deferErr error
db, err := getJrpDB(ctx, h.connManager)
if err != nil {
return nil, err
}
args := make([]interface{}, 0, len(keywords)+2)
whereClause := ""
for i, keyword := range keywords {
if i == 0 {
whereClause += "Phrase LIKE ?"
} else {
if and {
whereClause += " AND Phrase LIKE ?"
} else {
whereClause += " OR Phrase LIKE ?"
}
}
args = append(args, "%"+keyword+"%")
}
args = append(args, isFavorited, number)
query := fmt.Sprintf(FindTopNByIsFavoritedIsAndByPhraseContainsOrderByIdAscQuery, whereClause)
rows, err := db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer func() {
deferErr = rows.Close()
}()
histories := []*history.History{}
for rows.Next() {
history := &history.History{}
if err := rows.Scan(
&history.ID,
&history.Phrase,
&history.Prefix,
&history.Suffix,
&history.IsFavorited,
&history.CreatedAt,
&history.UpdatedAt,
); err != nil {
return nil, err
}
histories = append(histories, history)
}
return histories, deferErr
}
// FindTopNByOrderByIdAsc is a method that finds the top N jrps from the history table by order by ID ascending.
func (h *historyRepository) FindTopNByOrderByIdAsc(ctx context.Context, number int) ([]*history.History, error) {
var deferErr error
db, err := getJrpDB(ctx, h.connManager)
if err != nil {
return nil, err
}
rows, err := db.QueryContext(ctx, FindTopNByOrderByIdAscQuery, number)
if err != nil {
return nil, err
}
defer func() {
deferErr = rows.Close()
}()
histories := []*history.History{}
for rows.Next() {
history := &history.History{}
if err := rows.Scan(
&history.ID,
&history.Phrase,
&history.Prefix,
&history.Suffix,
&history.IsFavorited,
&history.CreatedAt,
&history.UpdatedAt,
); err != nil {
return nil, err
}
histories = append(histories, history)
}
return histories, deferErr
}
// FindTopNByPhraseContainsOrderByIdAsc is a method that finds the top N jrps from the history table by phrase contains order by ID ascending.
func (h *historyRepository) FindTopNByPhraseContainsOrderByIdAsc(
ctx context.Context,
keywords []string,
and bool,
number int,
) ([]*history.History, error) {
var deferErr error
db, err := getJrpDB(ctx, h.connManager)
if err != nil {
return nil, err
}
args := make([]interface{}, 0, len(keywords)+1)
whereClause := ""
for i, keyword := range keywords {
if i == 0 {
whereClause += "Phrase LIKE ?"
} else {
if and {
whereClause += " AND Phrase LIKE ?"
} else {
whereClause += " OR Phrase LIKE ?"
}
}
args = append(args, "%"+keyword+"%")
}
args = append(args, number)
query := fmt.Sprintf(FindTopNByPhraseContainsOrderByIdAscQuery, whereClause)
rows, err := db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer func() {
deferErr = rows.Close()
}()
histories := []*history.History{}
for rows.Next() {
history := &history.History{}
if err := rows.Scan(
&history.ID,
&history.Phrase,
&history.Prefix,
&history.Suffix,
&history.IsFavorited,
&history.CreatedAt,
&history.UpdatedAt,
); err != nil {
return nil, err
}
histories = append(histories, history)
}
return histories, deferErr
}
// SaveAll is a method that saves all the jrp to the history table.
func (h *historyRepository) SaveAll(ctx context.Context, jrps []*history.History) ([]*history.History, error) {
if len(jrps) == 0 {
return jrps, nil
}
valueStrings := make([]string, 0, len(jrps))
valueArgs := make([]interface{}, 0, len(jrps)*6)
for _, jrp := range jrps {
valueStrings = append(valueStrings, "(?, ?, ?, ?, ?, ?)")
valueArgs = append(valueArgs,
jrp.Phrase,
jrp.Prefix,
jrp.Suffix,
jrp.IsFavorited,
jrp.CreatedAt,
jrp.UpdatedAt,
)
}
var deferErr error
db, err := getJrpDB(ctx, h.connManager)
if err != nil {
return nil, err
}
tx, err := db.BeginTx(
ctx,
&sql.TxOptions{
Isolation: sql.LevelSerializable,
ReadOnly: false,
},
)
if err != nil {
return nil, err
}
defer func() {
deferErr = tx.Rollback()
}()
query := fmt.Sprintf(InsertQuery, strings.Join(valueStrings, ","))
result, err := tx.ExecContext(ctx, query, valueArgs...)
if err != nil {
return nil, err
}
lastID, err := result.LastInsertId()
if err != nil {
return nil, err
}
firstID := lastID - int64(len(jrps)) + 1
for i, jrp := range jrps {
jrp.ID = int(firstID) + i
}
if err := tx.Commit(); err != nil {
return nil, err
}
return jrps, deferErr
}
// UpdateIsFavoritedByIdIn is a method that updates the is favorited of the jrps from the history table by ID in.
func (h *historyRepository) UpdateIsFavoritedByIdIn(
ctx context.Context,
isFavorited int,
ids []int,
) (int, error) {
var deferErr error
db, err := getJrpDB(ctx, h.connManager)
if err != nil {
return 0, err
}
args := make([]interface{}, 0, len(ids))
for _, id := range ids {
args = append(args, id)
}
query := fmt.Sprintf(UpdateIsFavoritedByIdInQuery, strings.Trim(strings.Repeat("?,", len(ids)), ","))
var result proxy.Result
if result, err = db.ExecContext(ctx, query, append([]interface{}{isFavorited}, args...)...); err != nil {
return 0, err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return 0, err
}
return int(rowsAffected), deferErr
}
// UpdateIsFavoritedByIsFavoritedIs is a method that updates the is favorited of the jrps from the history table by is favorited is.
func (h *historyRepository) UpdateIsFavoritedByIsFavoritedIs(
ctx context.Context,
isFavorited int,
isFavoritedIs int,
) (int, error) {
var deferErr error
db, err := getJrpDB(ctx, h.connManager)
if err != nil {
return 0, err
}
var result proxy.Result
if result, err = db.ExecContext(ctx, UpdateIsFavoritedByIsFavoritedIsQuery, isFavorited, isFavoritedIs); err != nil {
return 0, err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return 0, err
}
return int(rowsAffected), deferErr
}
// getJrpDB is a function that returns the jrp database connection.
func getJrpDB(ctx context.Context, connManager database.ConnectionManager) (proxy.DB, error) {
var deferErr error
conn, err := connManager.GetConnection(database.JrpDB)
if err != nil {
return nil, err
}
db, err := conn.Open()
if err != nil {
return nil, err
}
if _, err := db.ExecContext(ctx, CreateQuery); err != nil {
return nil, err
}
return db, deferErr
}
package query_service
import (
"context"
"fmt"
"strings"
"github.com/yanosea/jrp/v2/app/application/wnjpn"
"github.com/yanosea/jrp/v2/app/infrastructure/database"
)
// WordQueryService is a struct that implements the WordQueryService interface.
type wordQueryService struct {
connManager database.ConnectionManager
}
// NewWordQueryService returns a new instance of the WordQueryService struct.
func NewWordQueryService() wnjpn.WordQueryService {
return &wordQueryService{
connManager: database.GetConnectionManager(),
}
}
// FindByLangAndPosIn is a method that fetches words by lang and pos.
func (w *wordQueryService) FindByLangIsAndPosIn(
ctx context.Context,
lang string,
pos []string,
) ([]*wnjpn.FetchWordsDto, error) {
var deferErr error
conn, err := w.connManager.GetConnection(database.WNJpnDB)
if err != nil {
return nil, err
}
db, err := conn.Open()
if err != nil {
return nil, err
}
placeholders := make([]string, len(pos))
for i := range pos {
placeholders[i] = "?"
}
query := fmt.Sprintf(FindByLangIsAndPosInQuery, strings.Join(placeholders, ","))
params := make([]interface{}, 0, len(pos)+1)
params = append(params, lang)
for _, p := range pos {
params = append(params, p)
}
rows, err := db.QueryContext(ctx, query, params...)
if err != nil {
return nil, err
}
defer func() {
deferErr = rows.Close()
}()
words := make([]*wnjpn.FetchWordsDto, 0)
for rows.Next() {
word := &wnjpn.FetchWordsDto{}
if err := rows.Scan(
&word.WordID,
&word.Lang,
&word.Lemma,
&word.Pron,
&word.Pos,
); err != nil {
return nil, err
}
words = append(words, word)
}
return words, deferErr
}
package config
import (
"path/filepath"
"strings"
baseConfig "github.com/yanosea/jrp/v2/app/config"
"github.com/yanosea/jrp/v2/app/infrastructure/database"
"github.com/yanosea/jrp/v2/pkg/proxy"
"github.com/yanosea/jrp/v2/pkg/utility"
)
// JrpServerConfigurator is an interface that gets the configuration of the Jrp server application.
type JrpServerConfigurator interface {
GetConfig() (*JrpServerConfig, error)
}
// ServerConfigurator is a struct that implements the JrpServerConfigurator interface.
type ServerConfigurator struct {
*baseConfig.BaseConfigurator
}
// NewJrpServerConfigurator creates a new JrpServerConfigurator.
func NewJrpServerConfigurator(
envconfigProxy proxy.Envconfig,
fileUtil utility.FileUtil,
) JrpServerConfigurator {
return &ServerConfigurator{
BaseConfigurator: baseConfig.NewConfigurator(
envconfigProxy,
fileUtil,
),
}
}
// JrpServerConfig is a struct that contains the configuration of the Jrp server application.
type JrpServerConfig struct {
baseConfig.JrpConfig
JrpPort string
}
// envConfig is a struct that contains the environment variables.
type envConfig struct {
JrpPort string `envconfig:"JRP_SERVER_PORT" default:"8080"`
WnJpnDBType database.DBType `envconfig:"JRP_SERVER_WNJPN_DB_TYPE" default:"sqlite"`
WnJpnDBDsn string `envconfig:"JRP_SERVER_WNJPN_DB" default:"XDG_DATA_HOME/jrp/wnjpn.db"`
}
// GetConfig gets the configuration of the Jrp server application.
func (c *ServerConfigurator) GetConfig() (*JrpServerConfig, error) {
var env envConfig
if err := c.Envconfig.Process("", &env); err != nil {
return nil, err
}
config := &JrpServerConfig{
JrpConfig: baseConfig.JrpConfig{
WNJpnDBType: env.WnJpnDBType,
WNJpnDBDsn: env.WnJpnDBDsn,
},
JrpPort: env.JrpPort,
}
if config.WNJpnDBType == database.SQLite {
xdgDataHome, err := c.FileUtil.GetXDGDataHome()
if err != nil {
return nil, err
}
config.WNJpnDBDsn = strings.Replace(
config.WNJpnDBDsn,
"XDG_DATA_HOME",
xdgDataHome,
1,
)
if err := c.FileUtil.MkdirIfNotExist(
filepath.Dir(config.WNJpnDBDsn),
); err != nil {
return nil, err
}
}
return config, nil
}
package formatter
import (
"errors"
)
// Formatter is an interface that formats the output of jrp cli.
type Formatter interface {
Format(result interface{}) ([]byte, error)
}
// NewFormatter returns a new instance of the Formatter interface.
func NewFormatter(
format string,
) (Formatter, error) {
var f Formatter
switch format {
case "json":
f = NewJsonFormatter()
default:
return nil, errors.New("invalid format")
}
return f, nil
}
package formatter
import (
"errors"
jrpApp "github.com/yanosea/jrp/v2/app/application/jrp"
"github.com/yanosea/jrp/v2/pkg/proxy"
"github.com/yanosea/jrp/v2/pkg/utility"
)
// JsonFormatter is a struct that formats the output of jrp server.
type JsonFormatter struct{}
// ResponseOutputDto is a struct that represents the response of jrp server.
type ResponseOutputDto struct {
Body []byte `json:"body"`
}
// @Description response format for jrp
// JrpJsonOutputDto is a struct that represents the output json of jrp server.
type JrpJsonOutputDto struct {
// @Description Generated Japanese phrase
Phrase string `json:"phrase"`
}
var (
// Ju is a variable that contains the JsonUtil struct for injecting dependencies in testing.
Ju = utility.NewJsonUtil(proxy.NewJson())
)
// NewJsonFormatter returns a new instance of the JsonFormatter struct.
func NewJsonFormatter() *JsonFormatter {
return &JsonFormatter{}
}
// Format formats the output of jrp server.
func (f *JsonFormatter) Format(result interface{}) ([]byte, error) {
var formatted []byte
var err error
switch v := result.(type) {
case *jrpApp.GenerateJrpUseCaseOutputDto:
jjoDto := JrpJsonOutputDto{Phrase: v.Phrase}
gjj, err := Ju.Marshal(jjoDto)
if err != nil {
return nil, err
}
gjroDto := ResponseOutputDto{Body: gjj}
formatted = gjroDto.Body
default:
formatted = nil
err = errors.New("invalid result type")
}
return formatted, err
}
package main
// @title JRP API
// @description jrp api server
// @host localhost:8080
// @BasePath /api
import (
"os"
"github.com/yanosea/jrp/v2/app/presentation/api/jrp-server/server"
_ "github.com/yanosea/jrp/v2/docs"
"github.com/yanosea/jrp/v2/pkg/proxy"
"github.com/yanosea/jrp/v2/pkg/utility"
)
// JrpApiServerParams is a struct that represents the options of jrp api sever.
type JrpApiServerParams struct {
// Echos is a proxy of labstack/echo/v4.
Echos proxy.Echos
// Envconfig is a proxy of kelseyhightower/envconfig.
Envconfig proxy.Envconfig
// FileUtil provides the file utility.
FileUtil utility.FileUtil
// Sql is a proxy of database/sql.
Sql proxy.Sql
}
var (
// exit is a variable that contains the os.Exit function for injecting dependencies in testing.
exit = os.Exit
// jrpApiServerParams is a variable that contains the jrpApiServerParams struct.
jrpApiServerParams = JrpApiServerParams{
Echos: proxy.NewEchos(),
Envconfig: proxy.NewEnvconfig(),
FileUtil: utility.NewFileUtil(
proxy.NewGzip(),
proxy.NewIo(),
proxy.NewOs(),
),
Sql: proxy.NewSql(),
}
)
// main is the entry point of jrp api server.
func main() {
serv := server.NewServer(
jrpApiServerParams.Echos,
)
if exitCode := serv.Init(
jrpApiServerParams.Envconfig,
jrpApiServerParams.FileUtil,
jrpApiServerParams.Sql,
); exitCode != 0 {
exit(exitCode)
}
exit(serv.Run())
}
package jrp
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/gommon/log"
jrpApp "github.com/yanosea/jrp/v2/app/application/jrp"
wnjpnApp "github.com/yanosea/jrp/v2/app/application/wnjpn"
"github.com/yanosea/jrp/v2/app/infrastructure/database"
"github.com/yanosea/jrp/v2/app/infrastructure/wnjpn/query_service"
"github.com/yanosea/jrp/v2/app/presentation/api/jrp-server/formatter"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
var (
format = "json"
)
// BindGetJrpHandler binds the getJrp handler to the server.
func BindGetJrpHandler(g proxy.Group) {
g.GET("/jrp", getJrp)
}
// @Summary get a random Japanese phrase.
// @Description returns a randomly generated Japanese phrase.
// @Tags jrp
// @Produce json
// @Success 200 {object} formatter.JrpJsonOutputDto
// @Router /jrp [get]
// getJrp is a handler that returns a random Japanese phrase.
func getJrp(c echo.Context) error {
connManager := database.GetConnectionManager()
if connManager == nil {
log.Error("Connection manager is not initialized...")
return c.NoContent(http.StatusInternalServerError)
}
if _, err := connManager.GetConnection(database.WNJpnDB); err != nil {
log.Error("Failed to get a connection to the database...")
return c.NoContent(http.StatusInternalServerError)
}
wordQueryService := query_service.NewWordQueryService()
fwuc := wnjpnApp.NewFetchWordsUseCase(wordQueryService)
fwoDtos, err := fwuc.Run(
c.Request().Context(),
"jpn",
[]string{"a", "v", "n"},
)
if err != nil {
log.Error("Failed to fetch words...")
return c.NoContent(http.StatusInternalServerError)
}
var gjiDtos []*jrpApp.GenerateJrpUseCaseInputDto
for _, fwoDto := range fwoDtos {
gjiDto := &jrpApp.GenerateJrpUseCaseInputDto{
WordID: fwoDto.WordID,
Lang: fwoDto.Lang,
Lemma: fwoDto.Lemma,
Pron: fwoDto.Pron,
Pos: fwoDto.Pos,
}
gjiDtos = append(gjiDtos, gjiDto)
}
gjuc := jrpApp.NewGenerateJrpUseCase()
gjoDto := gjuc.RunWithRandom(gjiDtos)
f, err := formatter.NewFormatter(format)
if err != nil {
log.Error("Failed to create a new formatter...")
return c.NoContent(http.StatusInternalServerError)
}
body, err := f.Format(gjoDto)
if body == nil || err != nil {
log.Error("Failed to format the output...")
return c.NoContent(http.StatusInternalServerError)
}
return c.JSONBlob(http.StatusOK, body)
}
package server
import (
"github.com/swaggo/echo-swagger"
"github.com/yanosea/jrp/v2/app/presentation/api/jrp-server/server/jrp"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// Bind binds the routes to the server.
func Bind(e proxy.Echo) {
e.Get("/swagger/*", echoSwagger.WrapHandler)
apiGroup := e.Group("/api")
jrp.BindGetJrpHandler(apiGroup)
}
package server
import (
"errors"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/yanosea/jrp/v2/app/infrastructure/database"
"github.com/yanosea/jrp/v2/app/presentation/api/jrp-server/config"
"github.com/yanosea/jrp/v2/pkg/proxy"
"github.com/yanosea/jrp/v2/pkg/utility"
)
var (
// NewServer is a variable holding the current server creation function.
NewServer CreateServerFunc = newServer
)
// Server is an interface that provides a proxy of the methods of jrp server.
type Server interface {
Init(envconfig proxy.Envconfig, fileUtil utility.FileUtil, sql proxy.Sql) int
Run() int
}
// server is a struct that represents the server interface of jrp server.
type server struct {
ConnectionManager database.ConnectionManager
Echos proxy.Echos
Logger proxy.Logger
Port string
Route proxy.Echo
}
// CreateServerFunc is a function type for creating new server instances.
type CreateServerFunc func(echo proxy.Echos) Server
// newServer is the default implementation of CreateServerFunc.
func newServer(echos proxy.Echos) Server {
return &server{
ConnectionManager: nil,
Echos: echos,
Logger: nil,
Port: "",
Route: nil,
}
}
// Init initializes the server.
func (s *server) Init(
envconfig proxy.Envconfig,
fileUtil utility.FileUtil,
sql proxy.Sql,
) int {
s.Route, s.Logger = s.Echos.NewEcho()
s.Route.Use(middleware.Logger())
s.Route.Use(middleware.Recover())
Bind(s.Route)
configurator := config.NewJrpServerConfigurator(envconfig, fileUtil)
conf, err := configurator.GetConfig()
if err != nil {
s.Logger.Fatal(err)
return 1
}
s.Port = conf.JrpPort
if s.Port == "8080" {
s.Route.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowMethods: []string{echo.GET},
}))
}
if s.ConnectionManager == nil {
s.ConnectionManager = database.NewConnectionManager(sql)
}
dbConfig := database.ConnectionConfig{
DBName: database.WNJpnDB,
DBType: conf.WNJpnDBType,
DSN: conf.WNJpnDBDsn,
}
if conf.WNJpnDBType == database.SQLite && !fileUtil.IsExist(conf.WNJpnDBDsn) {
s.Logger.Fatal(errors.New("wnjpn database file not found"))
return 1
}
if err := s.ConnectionManager.InitializeConnection(dbConfig); err != nil {
s.Logger.Fatal(err)
return 1
}
return 0
}
// Run runs the server.
func (s *server) Run() (exitCode int) {
defer func() {
if s.ConnectionManager != nil {
if err := s.ConnectionManager.CloseAllConnections(); err != nil {
s.Logger.Fatal(err)
exitCode = 1
}
}
}()
if err := s.Route.Start(":" + s.Port); err != nil {
s.Logger.Fatal(err)
exitCode = 1
}
return
}
package command
import (
"context"
"os"
"github.com/yanosea/jrp/v2/app/infrastructure/database"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/config"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/formatter"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/presenter"
"github.com/yanosea/jrp/v2/pkg/proxy"
"github.com/yanosea/jrp/v2/pkg/utility"
)
var (
// output is the output string.
output = ""
// NewCli is a variable holding the current Cli creation function.
NewCli CreateCliFunc = newCli
)
type Cli interface {
Init(envconfig proxy.Envconfig, sql proxy.Sql, version string, fileUtil utility.FileUtil, versionUtil utility.VersionUtil) int
Run(ctx context.Context) int
}
// cli is a struct that represents the command line interface of jrp cli.
type cli struct {
Cobra proxy.Cobra
RootCommand proxy.Command
ConnectionManager database.ConnectionManager
}
// CreateCliFunc is a function type for creating new Cli instances.
type CreateCliFunc func(cobra proxy.Cobra) Cli
// newCli is the default implementation of CreateCliFunc.
func newCli(cobra proxy.Cobra) Cli {
return &cli{
Cobra: cobra,
RootCommand: nil,
ConnectionManager: nil,
}
}
// Init initializes the command line interface of jrp.
func (c *cli) Init(
envconfig proxy.Envconfig,
sql proxy.Sql,
version string,
fileUtil utility.FileUtil,
versionUtil utility.VersionUtil,
) int {
configurator := config.NewJrpCliConfigurator(envconfig, fileUtil)
conf, err := configurator.GetConfig()
if err != nil {
output = formatter.AppendErrorToOutput(err, output)
if err := presenter.Print(os.Stderr, output); err != nil {
return 1
}
return 1
}
if c.ConnectionManager == nil {
c.ConnectionManager = database.NewConnectionManager(sql)
}
if conf.JrpDBType == database.SQLite {
if err := c.ConnectionManager.InitializeConnection(
database.ConnectionConfig{
DBName: database.JrpDB,
DBType: conf.JrpDBType,
DSN: conf.JrpDBDsn,
},
); err != nil {
output = formatter.AppendErrorToOutput(err, output)
if err := presenter.Print(os.Stderr, output); err != nil {
return 1
}
return 1
}
}
if conf.WNJpnDBType == database.SQLite && fileUtil.IsExist(conf.WNJpnDBDsn) {
if err := c.ConnectionManager.InitializeConnection(
database.ConnectionConfig{
DBName: database.WNJpnDB,
DBType: conf.WNJpnDBType,
DSN: conf.WNJpnDBDsn,
},
); err != nil {
output = formatter.AppendErrorToOutput(err, output)
if err := presenter.Print(os.Stderr, output); err != nil {
return 1
}
return 1
}
}
c.RootCommand = NewRootCommand(
c.Cobra,
versionUtil.GetVersion(version),
conf,
&output,
)
return 0
}
// Run runs the command line interface of jrp cli.
func (c *cli) Run(ctx context.Context) (exitCode int) {
defer func() {
if c.ConnectionManager != nil {
if err := c.ConnectionManager.CloseAllConnections(); err != nil {
output = formatter.AppendErrorToOutput(err, output)
if err := presenter.Print(os.Stderr, output); err != nil {
exitCode = 1
}
exitCode = 1
}
}
}()
out := os.Stdout
if err := c.RootCommand.ExecuteContext(ctx); err != nil {
output = formatter.AppendErrorToOutput(err, output)
out = os.Stderr
exitCode = 1
}
if err := presenter.Print(out, output); err != nil {
exitCode = 1
}
return
}
package completion
import (
"bytes"
c "github.com/spf13/cobra"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// NewCompletionBashCommand returns a new instance of the completion bash command.
func NewCompletionBashCommand(
cobra proxy.Cobra,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("bash")
cmd.SetAliases([]string{"ba", "b"})
cmd.SetUsageTemplate(completionBashUsageTemplate)
cmd.SetHelpTemplate(completionBashHelpTemplate)
cmd.SetArgs(cobra.ExactArgs(0))
cmd.SetSilenceErrors(true)
cmd.SetRunE(
func(cmd *c.Command, args []string) error {
return runCompletionBash(cmd, output)
},
)
return cmd
}
// runCompletionBash generates the autocompletion script for the bash shell.
func runCompletionBash(cmd *c.Command, output *string) error {
buf := new(bytes.Buffer)
err := cmd.Root().GenBashCompletion(buf)
*output = buf.String()
return err
}
const (
// completionBashHelpTemplate is the help template of the completion bash command.
completionBashHelpTemplate = `๐ง๐ Generate the autocompletion script for the bash shell.
This script depends on the "bash-completion" package.
If it is not installed already, you can install it via your OS's package manager.
To load completions in your current shell session:
source <(jrp completion bash)
To load completions for every new session, execute once:
- ๐ง Linux:
jrp completion bash > /etc/bash_completion.d/jrp
- ๐ macOS:
jrp completion bash > $(brew --prefix)/etc/bash_completion.d/jrp
You will need to start a new shell for this setup to take effect.
` + completionBashUsageTemplate
// compleitonUsageTemplate is the usage template of the completion bash command.
completionBashUsageTemplate = `Usage:
jrp completion bash [flags]
Flags:
-h, --help ๐ค help for bash
`
)
package completion
import (
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// NewCompletionCommand returns a new instance of the completion command.
func NewCompletionCommand(
cobra proxy.Cobra,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("completion")
cmd.SetAliases([]string{"comp", "c"})
cmd.SetUsageTemplate(completionUsageTemplate)
cmd.SetHelpTemplate(completionHelpTemplate)
cmd.SetArgs(cobra.ExactArgs(0))
cmd.SetSilenceErrors(true)
cmd.AddCommand(
NewCompletionBashCommand(cobra, output),
NewCompletionFishCommand(cobra, output),
NewCompletionPowerShellCommand(cobra, output),
NewCompletionZshCommand(cobra, output),
)
return cmd
}
const (
// completionHelpTemplate is the help template of the completion command.
completionHelpTemplate = `๐ง Generate the autocompletion script for the specified shell.
` + completionUsageTemplate
// compleitonUsageTemplate is the usage template of the completion command.
completionUsageTemplate = `Usage:
jrp completion [flags]
jrp completion [command]
Available Subommands:
bash ๐ง๐ Generate the autocompletion script for the bash shell.
fish ๐ง๐ Generate the autocompletion script for the fish shell.
powershell ๐ง๐ช Generate the autocompletion script for the powershell shell.
zsh ๐ง๐ง Generate the autocompletion script for the zsh shell.
Flags:
-h, --help ๐ค help for completion
`
)
package completion
import (
"bytes"
c "github.com/spf13/cobra"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// NewCompletionFishCommand returns a new instance of the completion fish command.
func NewCompletionFishCommand(
cobra proxy.Cobra,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("fish")
cmd.SetAliases([]string{"fi", "f"})
cmd.SetUsageTemplate(completionFishUsageTemplate)
cmd.SetHelpTemplate(completionFishHelpTemplate)
cmd.SetArgs(cobra.ExactArgs(0))
cmd.SetSilenceErrors(true)
cmd.SetRunE(
func(cmd *c.Command, args []string) error {
return runCompletionFish(cmd, output)
},
)
return cmd
}
// runCompletionFish generates the autocompletion script for the fish shell.
func runCompletionFish(cmd *c.Command, output *string) error {
buf := new(bytes.Buffer)
err := cmd.Root().GenFishCompletion(buf, true)
*output = buf.String()
return err
}
const (
// completionFishHelpTemplate is the help template of the completion fish command.
completionFishHelpTemplate = `๐ง๐ Generate the autocompletion script for the fish shell.
To load completions in your current shell session:
jrp completion fish | source
To load completions for every new session, execute once:
jrp completion fish > ~/.config/fish/completions/jrp.fish
You will need to start a new shell for this setup to take effect.
` + completionFishUsageTemplate
// compleitonUsageTemplate is the usage template of the completion fish command.
completionFishUsageTemplate = `Usage:
jrp completion fish [flags]
Flags:
-h, --help ๐ค help for fish
`
)
package completion
import (
"bytes"
c "github.com/spf13/cobra"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// NewCompletionPowerShellCommand returns a new instance of the completion powershell command.
func NewCompletionPowerShellCommand(
cobra proxy.Cobra,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("powershell")
cmd.SetAliases([]string{"pwsh", "p"})
cmd.SetUsageTemplate(completionPowerShellUsageTemplate)
cmd.SetHelpTemplate(completionPowerShellHelpTemplate)
cmd.SetArgs(cobra.ExactArgs(0))
cmd.SetSilenceErrors(true)
cmd.SetRunE(
func(cmd *c.Command, args []string) error {
return runCompletionPowerShell(cmd, output)
},
)
return cmd
}
// runCompletionPowerShell generates the autocompletion script for the powershell shell.
func runCompletionPowerShell(cmd *c.Command, output *string) error {
buf := new(bytes.Buffer)
err := cmd.Root().GenPowerShellCompletion(buf)
*output = buf.String()
return err
}
const (
// completionPowerShellHelpTemplate is the help template of the completion powershell command.
completionPowerShellHelpTemplate = `๐ง๐ช Generate the autocompletion script for the powershell shell.
To load completions in your current shell session:
jrp completion powershell | Out-String | Invoke-Expression
To load completions for every new session, add the output of the above command to your powershell profile.
` + completionPowerShellUsageTemplate
// compleitonUsageTemplate is the usage template of the completion powershell command.
completionPowerShellUsageTemplate = `Usage:
jrp completion powershell [flags]
Flags:
-h, --help ๐ค help for powershell
`
)
package completion
import (
"bytes"
c "github.com/spf13/cobra"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// NewCompletionZshCommand returns a new instance of the completion zsh command.
func NewCompletionZshCommand(
cobra proxy.Cobra,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("zsh")
cmd.SetAliases([]string{"zs", "z"})
cmd.SetUsageTemplate(completionZshUsageTemplate)
cmd.SetHelpTemplate(completionZshHelpTemplate)
cmd.SetArgs(cobra.ExactArgs(0))
cmd.SetSilenceErrors(true)
cmd.SetRunE(
func(cmd *c.Command, args []string) error {
return runCompletionZsh(cmd, output)
},
)
return cmd
}
// runCompletionZsh generates the autocompletion script for the zsh shell.
func runCompletionZsh(cmd *c.Command, output *string) error {
buf := new(bytes.Buffer)
err := cmd.Root().GenZshCompletion(buf)
*output = buf.String()
return err
}
const (
// completionZshHelpTemplate is the help template of the completion zsh command.
completionZshHelpTemplate = `๐ง๐ง Generate the autocompletion script for the zsh shell.
If shell completion is not already enabled in your environment you will need to enable it.
You can execute the following once:
echo "autoload -U compinit; compinit" >> ~/.zshrc
To load completions in your current shell session:
source <(jrp completion zsh)
To load completions for every new session, execute once:
- ๐ง Linux:
jrp completion zsh > "${fpath[1]}/_jrp"
- ๐ macOS:
jrp completion zsh > $(brew --prefix)/share/zsh/site-functions/_jrp
You will need to start a new shell for this setup to take effect.
` + completionZshUsageTemplate
// compleitonUsageTemplate is the usage template of the completion zsh command.
completionZshUsageTemplate = `Usage:
jrp completion zsh [flags]
Flags:
-h, --help ๐ค help for zsh
`
)
package jrp
import (
c "github.com/spf13/cobra"
jrpApp "github.com/yanosea/jrp/v2/app/application/jrp"
"github.com/yanosea/jrp/v2/app/infrastructure/database"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/config"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/formatter"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/presenter"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// NewDownloadCommand returns a new instance of the download command.
func NewDownloadCommand(
cobra proxy.Cobra,
conf *config.JrpCliConfig,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("download")
cmd.SetAliases([]string{"dl", "d"})
cmd.SetUsageTemplate(downloadUsageTemplate)
cmd.SetHelpTemplate(downloadHelpTemplate)
cmd.SetArgs(cobra.ExactArgs(0))
cmd.SetSilenceErrors(true)
cmd.SetRunE(
func(_ *c.Command, _ []string) error {
return runDownload(conf, output)
},
)
return cmd
}
// runDownload runs the download command.
func runDownload(conf *config.JrpCliConfig, output *string) error {
if conf.WNJpnDBType != database.SQLite {
o := formatter.Red("โ The type of WordNet Japan database is not sqlite...")
*output = o
return nil
}
if err := presenter.StartSpinner(
true,
"yellow",
formatter.Yellow(
" ๐ฆ Downloading WordNet Japan sqlite database file from the official web site...",
),
); err != nil {
o := formatter.Red("โ Failed to start spinner...")
*output = o
return err
}
defer func() {
presenter.StopSpinner()
}()
duc := jrpApp.NewDownloadUseCase()
if err := duc.Run(conf.WNJpnDBDsn); err != nil && err.Error() == "wnjpn.db already exists" {
o := formatter.Green("โ
You are already ready to use jrp!")
*output = o
return nil
} else if err != nil {
o := formatter.Red("โ Failed to download WordNet Japan sqlite database file...")
*output = o
return err
}
o := formatter.Green("โ
Downloaded successfully! Now, you are ready to use jrp!")
*output = o
return nil
}
const (
// downloadHelpTemplate is the help template of the download command.
downloadHelpTemplate = `๐ฆ Download WordNet Japan sqlite database file from the official web site.
You have to download WordNet Japan sqlite database file to use jrp at first.
"jrp download" will download archive file from the official web site and decompress it to the database file.
The default directory is "$XDG_DATA_HOME/jrp" ("~/.local/share/jrp").
If you want to change the directory, set the โJRP_WNJPN_DB_FILE_DIRโ environment variable.
You have to set the same directory to the โJRP_WNJPN_DB_FILE_DIRโ environment variable when you use jrp.
` + downloadUsageTemplate
// downloadUsageTemplate is the usage template of the download command.
downloadUsageTemplate = `Usage:
jrp download [flags]
jrp dl [flags]
jrp d [flags]
Flags:
-h, --help ๐ค help for jrp download
`
)
package jrp
import (
"strconv"
c "github.com/spf13/cobra"
jrpApp "github.com/yanosea/jrp/v2/app/application/jrp"
"github.com/yanosea/jrp/v2/app/infrastructure/jrp/repository"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/formatter"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/presenter"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// FavoriteOptions provides the options for the favorite command.
type FavoriteOptions struct {
// All is a flag to add all phrases.
All bool
// NoConfirm is a flag to not confirm before removing all the histories.
NoConfirm bool
}
var (
// favoriteOps is a variable to store the favorite options with the default values for injecting the dependencies in testing.
favoriteOps = FavoriteOptions{
All: false,
NoConfirm: false,
}
)
// NewFavoriteCommand returns a new instance of the favorite command.
func NewFavoriteCommand(
cobra proxy.Cobra,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("favorite")
cmd.SetAliases([]string{"fav", "f"})
cmd.SetUsageTemplate(favoriteUsageTemplate)
cmd.SetHelpTemplate(favoriteHelpTemplate)
cmd.SetSilenceErrors(true)
cmd.Flags().BoolVarP(
&favoriteOps.All,
"all",
"a",
false,
"โญ favorite all histories",
)
cmd.Flags().BoolVarP(
&favoriteOps.NoConfirm,
"no-confirm",
"",
false,
"๐ซ do not confirm before favoriting all the histories",
)
cmd.SetRunE(
func(cmd *c.Command, args []string) error {
return runFavorite(
cmd,
args,
output,
)
},
)
return cmd
}
// runFavorite runs the favorite command.
func runFavorite(
cmd *c.Command,
args []string,
output *string,
) error {
if len(args) == 0 && !favoriteOps.All {
o := formatter.Yellow("โก No ID arguments specified...")
*output = o
return nil
}
var ids []int
for _, arg := range args {
id, err := strconv.Atoi(arg)
if err != nil {
o := formatter.Red("๐จ The ID argument must be an integer...")
*output = o
return err
}
ids = append(ids, id)
}
historyRepo := repository.NewHistoryRepository()
fuc := jrpApp.NewFavoriteUseCase(historyRepo)
if favoriteOps.All && !favoriteOps.NoConfirm {
if answer, err := presenter.RunPrompt(
"Proceed with favoriting all the histories? [y/N]",
); err != nil {
return err
} else if answer != "y" && answer != "Y" {
o := formatter.Yellow("๐ซ Cancelled favoriting all the histories.")
*output = o
return nil
}
}
if err := fuc.Run(
cmd.Context(),
ids,
favoriteOps.All,
); err != nil && err.Error() == "no histories to favorite" {
o := formatter.Yellow("โก No histories to favorite...")
*output = o
return nil
} else if err != nil {
return err
}
o := formatter.Green("โ
Favorited successfully!")
*output = o
return nil
}
const (
// favoriteHelpTemplate is the help template of the favorite command.
favoriteHelpTemplate = `โญ Favorite the histories of the "generate" command.
You can specify the histories to favorite with ID arguments.
You have to get ID from the "history" command.
Multiple ID's can be specified separated by spaces.
This command can make the histories easier to find.
And you will not be able to remove the histories with executing "history remove" and "history clear".
Also, you can favorite all the histories with the "-a" or "--all" flag.
` + favoriteUsageTemplate
// favoriteUsageTemplate is the usage template of the favorite command.
favoriteUsageTemplate = `Usage:
jrp favorite [flag] [arguments]
jrp fav [flag] [arguments]
jrp f [flag] [arguments]
Flags:
-a, --all โญ favorite all the histories
-no-confirm ๐ซ do not confirm before favoriting all the histories
-h, --help ๐ค help for favorite
Arguments:
ID ๐ favorite with the ID of the history (e.g. : 1 2 3)
`
)
package generate
import (
"strconv"
c "github.com/spf13/cobra"
jrpApp "github.com/yanosea/jrp/v2/app/application/jrp"
wnjpnApp "github.com/yanosea/jrp/v2/app/application/wnjpn"
"github.com/yanosea/jrp/v2/app/infrastructure/database"
"github.com/yanosea/jrp/v2/app/infrastructure/jrp/repository"
"github.com/yanosea/jrp/v2/app/infrastructure/wnjpn/query_service"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/formatter"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// GenerateOptions provides the options for the generate command.
type GenerateOptions struct {
// Number is a flag to specify the number of phrases to generate.
Number int
// Prefix is a flag to specify the prefix of the phrases to generate.
Prefix string
// Suffix is a flag to specify the suffix of the phrases to generate.
Suffix string
// DryRun is a flag to generate phrases without saving to the history.
DryRun bool
// Format is a flag to specify the format of the output.
Format string
// Interactive is a flag to generate Japanese random phrases interactively.
Interactive bool
// Timeout is a flag to specify the timeout in seconds for the interactive mode.
Timeout int
}
var (
// GenerateOps is a variable to store the generate options with the default values for injecting the dependencies in testing.
GenerateOps = GenerateOptions{
Number: 1,
Prefix: "",
Suffix: "",
DryRun: false,
Format: "table",
Interactive: false,
Timeout: 30,
}
)
// NewGenerateCommand returns a new instance of the generate command.
func NewGenerateCommand(
cobra proxy.Cobra,
interactiveCmd proxy.Command,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("generate")
cmd.SetAliases([]string{"gen", "g"})
cmd.SetUsageTemplate(generateUsageTemplate)
cmd.SetHelpTemplate(generateHelpTemplate)
cmd.SetArgs(cobra.MaximumNArgs(1))
cmd.SetSilenceErrors(true)
cmd.Flags().IntVarP(
&GenerateOps.Number,
"number",
"n",
1,
"๐ข number of phrases to generate (default 1, e.g. : 10)",
)
cmd.Flags().StringVarP(
&GenerateOps.Prefix,
"prefix",
"p",
"",
"๐ก prefix of phrases to generate",
)
cmd.Flags().StringVarP(
&GenerateOps.Suffix,
"suffix",
"s",
"",
"๐ก suffix of phrases to generate",
)
cmd.Flags().BoolVarP(
&GenerateOps.DryRun,
"dry-run",
"d",
false,
"๐งช generate phrases without saving to the history",
)
cmd.Flags().StringVarP(
&GenerateOps.Format,
"format",
"f",
"table",
"๐ format of the output (default \"table\", e.g. : \"plain\")",
)
cmd.Flags().BoolVarP(
&GenerateOps.Interactive,
"interactive",
"i",
false,
"๐ฌ generate Japanese random phrases interactively",
)
cmd.Flags().IntVarP(
&GenerateOps.Timeout,
"timeout",
"t",
30,
"โ timeout in seconds for the interactive mode (default 30, e.g. : 10)",
)
cmd.AddCommand(interactiveCmd)
cmd.SetRunE(
func(cmd *c.Command, args []string) error {
return runGenerate(
cmd,
args,
interactiveCmd,
output,
)
},
)
return cmd
}
// runGenerate runs the generate command.
func runGenerate(
cmd *c.Command,
args []string,
interactiveCmd proxy.Command,
output *string,
) error {
if GenerateOps.Interactive {
interactiveOps.Prefix = GenerateOps.Prefix
interactiveOps.Suffix = GenerateOps.Suffix
interactiveOps.Format = GenerateOps.Format
interactiveOps.Timeout = GenerateOps.Timeout
return interactiveCmd.RunE(cmd, args)
}
connManager := database.GetConnectionManager()
if connManager == nil {
o := formatter.Red("โ Connection manager is not initialized...")
*output = o
return nil
}
_, err := connManager.GetConnection(database.WNJpnDB)
if err != nil && err.Error() == "connection not initialized" {
o := formatter.Yellow("โก You have to execute \"download\" to use jrp...")
*output = o
return nil
} else if err != nil {
return err
}
needRandomPrefix := GenerateOps.Prefix == ""
needRandomSuffix := GenerateOps.Suffix == ""
if !needRandomPrefix && !needRandomSuffix {
o := formatter.Yellow("โก You can't specify both prefix and suffix at the same time...")
*output = o
return nil
}
var pos []string
if needRandomPrefix {
pos = append(pos, "a", "v")
}
if needRandomSuffix {
pos = append(pos, "n")
}
wordQueryService := query_service.NewWordQueryService()
fwuc := wnjpnApp.NewFetchWordsUseCase(wordQueryService)
fwoDtos, err := fwuc.Run(
cmd.Context(),
"jpn",
pos,
)
if err != nil {
return err
}
var number = GenerateOps.Number
if len(args) > 0 {
argNumber, err := strconv.Atoi(args[0])
if err != nil {
o := formatter.Red("๐จ The number argument must be an integer...")
*output = o
return err
}
if argNumber > number {
number = argNumber
}
}
var gjiDtos []*jrpApp.GenerateJrpUseCaseInputDto
for _, fwoDto := range fwoDtos {
gjiDto := &jrpApp.GenerateJrpUseCaseInputDto{
WordID: fwoDto.WordID,
Lang: fwoDto.Lang,
Lemma: fwoDto.Lemma,
Pron: fwoDto.Pron,
Pos: fwoDto.Pos,
}
gjiDtos = append(gjiDtos, gjiDto)
}
gjuc := jrpApp.NewGenerateJrpUseCase()
var gjoDtos []*jrpApp.GenerateJrpUseCaseOutputDto
for i := 0; i < number; i++ {
var gjoDto *jrpApp.GenerateJrpUseCaseOutputDto
if needRandomPrefix && needRandomSuffix {
gjoDto = gjuc.RunWithRandom(gjiDtos)
} else if needRandomPrefix {
gjoDto = gjuc.RunWithSuffix(gjiDtos, GenerateOps.Suffix)
} else {
gjoDto = gjuc.RunWithPrefix(gjiDtos, GenerateOps.Prefix)
}
gjoDtos = append(gjoDtos, gjoDto)
}
if !GenerateOps.DryRun {
var shiDtos []*jrpApp.SaveHistoryUseCaseInputDto
for _, gjoDto := range gjoDtos {
shiDto := &jrpApp.SaveHistoryUseCaseInputDto{
Phrase: gjoDto.Phrase,
Prefix: gjoDto.Prefix,
Suffix: gjoDto.Suffix,
IsFavorited: gjoDto.IsFavorited,
CreatedAt: gjoDto.CreatedAt,
UpdatedAt: gjoDto.UpdatedAt,
}
shiDtos = append(shiDtos, shiDto)
}
historyRepo := repository.NewHistoryRepository()
shuc := jrpApp.NewSaveHistoryUseCase(historyRepo)
shoDtos, err := shuc.Run(cmd.Context(), shiDtos)
if err != nil {
return err
}
if len(shoDtos) > 0 {
for i, gjoDto := range gjoDtos {
gjoDto.ID = shoDtos[i].ID
}
}
}
f, err := formatter.NewFormatter(GenerateOps.Format)
if err != nil {
o := formatter.Red("โ Failed to create a formatter...")
*output = o
return err
}
o := f.Format(gjoDtos)
*output = o
return nil
}
const (
// generateHelpTemplate is the help template of the generate command.
generateHelpTemplate = `โจ Generate Japanese random phrases.
You can specify how many phrases to generate by flag "-n" or "--number" or a number argument.
If both are provided, the larger number takes precedence.
And you can specify the prefix or suffix of the phrases to generate
by the flag "-p" or "--prefix" and "-s" or "--suffix".
Those commands below are the same.
"jrp" : "jrp generate"
"jrp interactive" : "jrp --interactive" : "jrp generate interactive" : "jrp generate --interactive"
` + generateUsageTemplate
// generateUsageTemplate is the usage template of the generate command.
generateUsageTemplate = `Usage:
jrp generate [flags] [argument]
jrp gen [flags] [argument]
jrp g [flags] [argument]
Available Subcommands:
interactive, int, i ๐ฌ Generate Japanese random phrases interactively.
Flags:
-n, --number ๐ข number of phrases to generate (default 1, e.g. : 10)
-p, --prefix ๐ก prefix of phrases to generate
-s, --suffix ๐ก suffix of phrases to generate
-d, --dry-run ๐งช generate phrases without saving to the history
-f, --format ๐ format of the output (default "table", e.g. : "plain")
-i, --interactive ๐ฌ generate Japanese random phrases interactively
-t, --timeout โ timeout in seconds for the interactive mode (default 30, e.g. : 10)
-h, --help ๐ค help for generate
Argument:
number ๐ข number of phrases to generate (default 1, e.g. : 10)
Use "jrp generate [command] --help" for more information about a command.
`
)
package generate
import (
"os"
"strconv"
c "github.com/spf13/cobra"
jrpApp "github.com/yanosea/jrp/v2/app/application/jrp"
wnjpnApp "github.com/yanosea/jrp/v2/app/application/wnjpn"
"github.com/yanosea/jrp/v2/app/infrastructure/database"
"github.com/yanosea/jrp/v2/app/infrastructure/jrp/repository"
"github.com/yanosea/jrp/v2/app/infrastructure/wnjpn/query_service"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/formatter"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/presenter"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// InteractiveOptions provides the options for the interactive command.
type InteractiveOptions struct {
// Prefix is a flag to specify the prefix of the phrases to generate.
Prefix string
// Suffix is a flag to specify the suffix of the phrases to generate.
Suffix string
// Format is a flag to specify the format of the output.
Format string
// Timeout is a flag to specify the timeout in seconds for the interactive mode.
Timeout int
}
var (
// interactiveOps is a variable to store the interactive options with the default values for injecting the dependencies in testing.
interactiveOps = InteractiveOptions{
Prefix: "",
Suffix: "",
Format: "table",
Timeout: 30,
}
)
// NewInteractiveCommand returns a new instance of the interactive command.
func NewInteractiveCommand(
cobra proxy.Cobra,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("interactive")
cmd.SetAliases([]string{"int", "i"})
cmd.SetUsageTemplate(interactiveUsageTemplate)
cmd.SetHelpTemplate(interactiveHelpTemplate)
cmd.SetArgs(cobra.MaximumNArgs(1))
cmd.SetSilenceErrors(true)
cmd.PersistentFlags().StringVarP(
&interactiveOps.Prefix,
"prefix",
"p",
"",
"๐ก prefix of phrases to generate",
)
cmd.PersistentFlags().StringVarP(
&interactiveOps.Suffix,
"suffix",
"s",
"",
"๐ก suffix of phrases to generate",
)
cmd.PersistentFlags().StringVarP(
&interactiveOps.Format,
"format",
"f",
"table",
"๐ format of the output (default \"table\", e.g: \"plain\")",
)
cmd.PersistentFlags().IntVarP(
&interactiveOps.Timeout,
"timeout",
"t",
30,
"โ timeout in seconds for the interactive mode (default 30, e.g: 10)",
)
cmd.SetRunE(
func(cmd *c.Command, _ []string) error {
return runInteractive(
cmd,
output,
)
},
)
return cmd
}
// runInteractive runs the interactive command.
func runInteractive(
cmd *c.Command,
output *string,
) error {
connManager := database.GetConnectionManager()
if connManager == nil {
o := formatter.Red("โ Connection manager is not initialized...")
*output = o
return nil
}
_, err := connManager.GetConnection(database.WNJpnDB)
if err != nil && err.Error() == "connection not initialized" {
o := formatter.Yellow("โก You have to execute \"download\" to use jrp...")
*output = o
return nil
} else if err != nil {
return err
}
needRandomPrefix := interactiveOps.Prefix == ""
needRandomSuffix := interactiveOps.Suffix == ""
if !needRandomPrefix && !needRandomSuffix {
o := formatter.Yellow("โก You can't specify both prefix and suffix at the same time...")
*output = o
return nil
}
var pos []string
if needRandomPrefix {
pos = append(pos, "a", "v")
}
if needRandomSuffix {
pos = append(pos, "n")
}
wordQueryService := query_service.NewWordQueryService()
fwuc := wnjpnApp.NewFetchWordsUseCase(wordQueryService)
fwoDtos, err := fwuc.Run(
cmd.Context(),
"jpn",
pos,
)
if err != nil {
return err
}
phase := 1
for {
if err := presenter.Print(os.Stdout, formatter.Blue("๐ Phase : "+strconv.Itoa(phase))); err != nil {
return err
}
var gjiDtos []*jrpApp.GenerateJrpUseCaseInputDto
for _, fwDto := range fwoDtos {
gjiDto := &jrpApp.GenerateJrpUseCaseInputDto{
WordID: fwDto.WordID,
Lang: fwDto.Lang,
Lemma: fwDto.Lemma,
Pron: fwDto.Pron,
Pos: fwDto.Pos,
}
gjiDtos = append(gjiDtos, gjiDto)
}
gjuc := jrpApp.NewGenerateJrpUseCase()
var gjoDtos []*jrpApp.GenerateJrpUseCaseOutputDto
var gjoDto *jrpApp.GenerateJrpUseCaseOutputDto
if needRandomPrefix && needRandomSuffix {
gjoDto = gjuc.RunWithRandom(gjiDtos)
} else if needRandomPrefix {
gjoDto = gjuc.RunWithSuffix(gjiDtos, GenerateOps.Suffix)
} else {
gjoDto = gjuc.RunWithPrefix(gjiDtos, GenerateOps.Prefix)
}
gjoDtos = append(gjoDtos, gjoDto)
f, err := formatter.NewFormatter(interactiveOps.Format)
if err != nil {
o := formatter.Red("โ Failed to create a formatter...")
*output = o
return err
}
o := f.Format(gjoDtos)
if err := presenter.Print(os.Stdout, "\n"); err != nil {
return err
}
if err := presenter.Print(os.Stdout, o); err != nil {
return err
}
if err := presenter.Print(os.Stdout, "\n"); err != nil {
return err
}
if err := presenter.Print(os.Stdout, formatter.Yellow(interactivePromptLabel)); err != nil {
return err
}
if err := presenter.OpenKeyboard(); err != nil {
return err
}
answer, err := presenter.GetKey(interactiveOps.Timeout)
if err != nil {
return err
}
if err := presenter.CloseKeyboard(); err != nil {
return err
}
var save bool
var cont bool
switch answer {
case "u", "U":
gjoDtos[0].IsFavorited = 1
save = true
cont = true
case "i", "I":
gjoDtos[0].IsFavorited = 1
save = true
cont = false
case "j", "J":
save = true
cont = true
case "k", "K":
save = true
cont = false
case "m", "M":
save = false
cont = true
default:
save = false
cont = false
}
if save {
var shiDtos []*jrpApp.SaveHistoryUseCaseInputDto
for _, gjoDto := range gjoDtos {
shiDto := &jrpApp.SaveHistoryUseCaseInputDto{
Phrase: gjoDto.Phrase,
Prefix: gjoDto.Prefix,
Suffix: gjoDto.Suffix,
IsFavorited: gjoDto.IsFavorited,
CreatedAt: gjoDto.CreatedAt,
UpdatedAt: gjoDto.UpdatedAt,
}
shiDtos = append(shiDtos, shiDto)
}
historyRepo := repository.NewHistoryRepository()
shuc := jrpApp.NewSaveHistoryUseCase(historyRepo)
_, err = shuc.Run(cmd.Context(), shiDtos)
if err != nil {
return err
}
if answer == "u" || answer == "U" || answer == "i" || answer == "I" {
if err := presenter.Print(os.Stdout, formatter.Green("โ
Favorited successfully!")); err != nil {
return err
}
if err := presenter.Print(os.Stdout, "\n"); err != nil {
return err
}
} else {
if err := presenter.Print(os.Stdout, formatter.Green("โ
Saved successfully!")); err != nil {
return err
}
if err := presenter.Print(os.Stdout, "\n"); err != nil {
return err
}
}
} else {
if err := presenter.Print(os.Stdout, formatter.Yellow("โฉ Skip!")); err != nil {
return err
}
if err := presenter.Print(os.Stdout, "\n"); err != nil {
return err
}
}
if !cont {
if err := presenter.Print(os.Stdout, "๐ช Exit!"); err != nil {
return err
}
break
}
phase++
}
return nil
}
const (
// interactiveHelpTemplate is the help template of the interactive command.
interactiveHelpTemplate = `๐ฌ Generate Japanese random phrases interactively.
You can specify the prefix or suffix of the phrases to generate
by the flag "-p" or "--prefix" and "-s" or "--suffix".
And you can choose to save or favorite the phrases generated interactively.
Press either key below for your action:
"u" : Favorite, continue.
"i" : Favorite, exit.
"j" : Save, continue.
"k" : Save, exit.
"m" : Skip, continue.
other : Skip, exit.
` + generateUsageTemplate
// interactiveUsageTemplate is the usage template of the interactive command.
interactiveUsageTemplate = `Usage:
jrp interactive [flags]
jrp int [flags]
jrp i [flags]
Flags:
-p, --prefix ๐ก prefix of phrases to generate
-s, --suffix ๐ก suffix of phrases to generate
-P, --plain ๐ plain text output instead of table output
-t, --timeout โ timeout second for the interactive mode (default 30, e.g: 10)
-h, --help ๐ค help for interactive
`
// interactivePromptLabel is the prompt label of the interactive command.
interactivePromptLabel = `๐ฝ Press either key below for your action:
"u" : Favorite, continue.
"i" : Favorite, exit.
"j" : Save, continue.
"k" : Save, exit.
"m" : Skip, continue.
other : Skip, exit.
`
)
package history
import (
c "github.com/spf13/cobra"
jrpApp "github.com/yanosea/jrp/v2/app/application/jrp"
"github.com/yanosea/jrp/v2/app/infrastructure/jrp/repository"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/formatter"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/presenter"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// ClearOptions provides the options for the clear command.
type ClearOptions struct {
// Force is a flag to clear the histories even if it is favorited.
Force bool
// NoConfirm is a flag to not confirm before removing all the histories.
NoConfirm bool
}
var (
// clearOps is a variable to store the clear options with the default values for injecting the dependencies in testing.
clearOps = ClearOptions{
Force: false,
NoConfirm: false,
}
)
// NewClearCommand returns a new instance of the clear command.
func NewClearCommand(
cobra proxy.Cobra,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("clear")
cmd.SetAliases([]string{"cl", "c"})
cmd.SetUsageTemplate(clearUsageTemplate)
cmd.SetHelpTemplate(clearHelpTemplate)
cmd.SetArgs(cobra.ExactArgs(0))
cmd.SetSilenceErrors(true)
cmd.Flags().BoolVarP(
&clearOps.Force,
"force",
"f",
false,
"๐ช clear the histories even if it is favorited",
)
cmd.Flags().BoolVarP(
&clearOps.NoConfirm,
"no-confirm",
"",
false,
"๐ซ do not confirm before clearing the histories",
)
cmd.SetRunE(
func(cmd *c.Command, _ []string) error {
return runClear(
cmd,
output,
)
},
)
return cmd
}
// runClear runs the clear command.
func runClear(
cmd *c.Command,
output *string,
) error {
var ids []int
historyRepo := repository.NewHistoryRepository()
rhuc := jrpApp.NewRemoveHistoryUseCase(historyRepo)
if !clearOps.NoConfirm {
if answer, err := presenter.RunPrompt(
"Proceed with clearing the histories? [y/N]",
); err != nil {
return err
} else if answer != "y" && answer != "Y" {
o := formatter.Yellow("๐ซ Cancelled clearing the histories.")
*output = o
return nil
}
}
if err := rhuc.Run(
cmd.Context(),
ids,
true,
clearOps.Force,
); err != nil && err.Error() == "no histories to remove" {
o := formatter.Yellow("โก No histories to clear...")
*output = o
return nil
} else if err != nil {
return err
}
o := formatter.Green("โ
Cleared successfully!")
*output = o
return nil
}
const (
// clearHelpTemplate is the help template of the clear command.
clearHelpTemplate = `๐โจ Clear the histories of the "generate" command.
You can clear the histories.
This is the same as the "history remove -a" command.
Also, you can clear the histories even if it is favorited by using the "-f" or ""--force" flag.
` + clearUsageTemplate
// clearUsageTemplate is the usage template of the clear command.
clearUsageTemplate = `Usage:
jrp history clear [flag]
jrp history cl [flag]
jrp history c [flag]
Flags:
-f, --force ๐ช clear the histories even if it is favorited
-no-confirm ๐ซ do not confirm before clearing the histories
-h, --help ๐ค help for clear
`
)
package history
import (
c "github.com/spf13/cobra"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// HistoryOptions provides the options for the generate command.
type HistoryOptions struct {
ShowOptions ShowOptions
}
var (
// historyOps is a variable to store the history options with the default values for injecting the dependencies in testing.
historyOps = HistoryOptions{
ShowOptions: ShowOptions{
Number: 1,
All: false,
Favorited: false,
Format: "table",
},
}
)
// NewHistoryCommand returns a new instance of the history command.
func NewHistoryCommand(
cobra proxy.Cobra,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("history")
cmd.SetAliases([]string{"hist", "h"})
cmd.SetUsageTemplate(historyUsageTemplate)
cmd.SetHelpTemplate(historyHelpTemplate)
cmd.SetArgs(cobra.MaximumNArgs(1))
cmd.SetSilenceErrors(true)
cmd.Flags().IntVarP(
&historyOps.ShowOptions.Number,
"number",
"n",
10,
"๐ข number how many histories to show (default 10, e.g. : 50)",
)
cmd.Flags().BoolVarP(
&historyOps.ShowOptions.All,
"all",
"a",
false,
"๐ show all the history",
)
cmd.Flags().BoolVarP(
&historyOps.ShowOptions.Favorited,
"favorited",
"F",
false,
"๐ show only favorited histories",
)
cmd.Flags().StringVarP(
&historyOps.ShowOptions.Format,
"format",
"f",
"table",
"๐ format of the output (default \"table\", e.g. : \"plain\")",
)
showCmd := NewShowCommand(
cobra,
output,
)
cmd.AddCommand(
NewClearCommand(
cobra,
output,
),
NewRemoveCommand(
cobra,
output,
),
NewSearchCommand(
cobra,
output,
),
showCmd,
)
cmd.SetRunE(
func(cmd *c.Command, args []string) error {
return runHistory(
cmd,
showCmd,
args,
)
},
)
return cmd
}
// runHistory runs the history command.
func runHistory(
cmd *c.Command,
showCmd proxy.Command,
args []string,
) error {
showOps = historyOps.ShowOptions
return showCmd.RunE(cmd, args)
}
const (
// historyHelpTemplate is the help template of the history command.
historyHelpTemplate = `๐ Manage the histories of the "generate" command.
You can show, search, remove and clear the histories of the "generate" command.
You can specify how many histories to show by flag "-n" or "--number" or a number argument.
jrp will get the most recent histories from the histories.
If you don't specify the number of histories, jrp will show the most recent 10 histories by default.
If both are provided, the larger number takes precedence.
Also, you can show all the histories the history by flag "-a" or "--all".
If you use the flag, the number flag or argument will be ignored.
` + historyUsageTemplate
// historyUsageTemplate is the usage template of the history command.
historyUsageTemplate = `Usage:
jrp history [flag] [argument]
jrp hist [flag] [argument]
jrp h [flag] [argument]
jrp history [command]
jrp hist [command]
jrp h [command]
Available Subommands:
show, sh, s ๐๐ Show the histories of the "generate" command.
You can abbreviate "show" sub command. ("jrp history" and "jrp history show" are the same.)
search, se, S ๐๐ Search the histories of the "generate" command.
remove, rm, r ๐๐งน Remove the histories of the "generate" command.
clear, cl, c ๐โจ Clear the histories of the "generate" command.
Flags:
-n, --number ๐ข number how many histories to show (default 10, e.g. : 50)
-a, --all ๐ show all the histories
-F, --favorited ๐ show only favorited histories
-f, --format ๐ format of the output (default "table", e.g. : "plain")
-h, --help ๐ค help for history
Argument:
number ๐ข number how many histories to show (default 10, e.g. : 50)
Use "jrp history [command] --help" for more information about a command.
`
)
package history
import (
"strconv"
c "github.com/spf13/cobra"
jrpApp "github.com/yanosea/jrp/v2/app/application/jrp"
"github.com/yanosea/jrp/v2/app/infrastructure/jrp/repository"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/formatter"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/presenter"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// RemoveOptions provides the options for the remove command.
type RemoveOptions struct {
// All is a flag to remove all phrases.
All bool
// Force is a flag to remove the histories even if it is favorited.
Force bool
// NoConfirm is a flag to not confirm before removing all the histories.
NoConfirm bool
}
var (
// removeOps is a variable to store the remove options with the default values for injecting the dependencies in testing.
removeOps = RemoveOptions{
All: false,
Force: false,
NoConfirm: false,
}
)
// NewRemoveCommand returns a new instance of the remove command.
func NewRemoveCommand(
cobra proxy.Cobra,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("remove")
cmd.SetAliases([]string{"rm", "r"})
cmd.SetUsageTemplate(removeUsageTemplate)
cmd.SetHelpTemplate(removeHelpTemplate)
cmd.SetSilenceErrors(true)
cmd.Flags().BoolVarP(
&removeOps.All,
"all",
"a",
false,
"โจ remove all history",
)
cmd.Flags().BoolVarP(
&removeOps.Force,
"force",
"f",
false,
"๐ช remove the histories even if it is favorited",
)
cmd.Flags().BoolVarP(
&removeOps.NoConfirm,
"no-confirm",
"",
false,
"๐ซ do not confirm before removing all the histories",
)
cmd.SetRunE(
func(cmd *c.Command, args []string) error {
return runRemove(
cmd,
args,
output,
)
},
)
return cmd
}
// runRemove runs the remove command.
func runRemove(
cmd *c.Command,
args []string,
output *string,
) error {
if len(args) == 0 && !removeOps.All {
o := formatter.Yellow("โก No ID arguments specified...")
*output = o
return nil
}
var ids []int
for _, arg := range args {
id, err := strconv.Atoi(arg)
if err != nil {
o := formatter.Red("๐จ The ID argument must be an integer...")
*output = o
return err
}
ids = append(ids, id)
}
historyRepo := repository.NewHistoryRepository()
rhuc := jrpApp.NewRemoveHistoryUseCase(historyRepo)
if removeOps.All && !removeOps.NoConfirm {
if answer, err := presenter.RunPrompt(
"Proceed with removing all the histories? [y/N]",
); err != nil {
return err
} else if answer != "y" && answer != "Y" {
o := formatter.Yellow("๐ซ Cancelled removing all the histories.")
*output = o
return nil
}
}
if err := rhuc.Run(
cmd.Context(),
ids,
removeOps.All,
removeOps.Force,
); err != nil && err.Error() == "no histories to remove" {
o := formatter.Yellow("โก No histories to remove...")
*output = o
return nil
} else if err != nil {
return err
}
o := formatter.Green("โ
Removed successfully!")
*output = o
return nil
}
const (
// removeHelpTemplate is the help template of the remove command.
removeHelpTemplate = `๐๐งน Remove the histories of the "generate" command.
You can specify the histories to remove with ID arguments.
You have to get ID from the "history" command.
Multiple ID's can be specified separated by spaces.
You can remove all the histories by flag "-a" or "--all".
This is the same as the "history clear" command.
Also, you can remove the histories even if it is favorited by using the "-f" or ""--force" flag.
` + removeUsageTemplate
// removeUsageTemplate is the usage template of the remove command.
removeUsageTemplate = `Usage:
jrp history remove [flag] [arguments]
jrp history rm [flag] [arguments]
jrp history r [flag] [arguments]
Flags:
-a, --all โจ remove all histories
-f, --force ๐ช remove the histories even if it is favorited
-no-confirm ๐ซ do not confirm before removing all the histories
-h, --help ๐ค help for remove
Arguments:
ID ๐ remove the history by the ID (e.g: 1 2 3)
`
)
package history
import (
c "github.com/spf13/cobra"
jrpApp "github.com/yanosea/jrp/v2/app/application/jrp"
"github.com/yanosea/jrp/v2/app/infrastructure/jrp/repository"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/formatter"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// SearchOptions provides the options for the search command.
type SearchOptions struct {
// Number is a flag to specify the number of histories to search.
Number int
// And is a flag to search histories by AND condition.
And bool
// All is a flag to search all histories.
All bool
// Favorited is a flag to show only favorited histories.
Favorited bool
// Format is a flag to specify the format of the output.
Format string
}
var (
// searchOps is a variable to store the search options with the default values for injecting the dependencies in testing.
searchOps = SearchOptions{
Number: 1,
And: false,
All: false,
Favorited: false,
Format: "table",
}
)
// NewSearchCommand returns a new instance of the search command.
func NewSearchCommand(
cobra proxy.Cobra,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("search")
cmd.SetAliases([]string{"se", "S"})
cmd.SetUsageTemplate(searchUsageTemplate)
cmd.SetHelpTemplate(searchHelpTemplate)
cmd.SetSilenceErrors(true)
cmd.Flags().IntVarP(
&searchOps.Number,
"number",
"n",
10,
"๐ข number how many histories to search (default 10, e.g: 50)",
)
cmd.Flags().BoolVarP(
&searchOps.And,
"and",
"A",
false,
"๐ง search histories by AND condition",
)
cmd.Flags().BoolVarP(
&searchOps.All,
"all",
"a",
false,
"๐ search all histories",
)
cmd.Flags().BoolVarP(
&searchOps.Favorited,
"favorited",
"F",
false,
"๐ show only favorited histories",
)
cmd.Flags().StringVarP(
&searchOps.Format,
"format",
"f",
"table",
"๐ format of the output (default \"table\", e.g: \"plain\")",
)
cmd.SetRunE(
func(cmd *c.Command, args []string) error {
return runSearch(
cmd,
args,
output,
)
},
)
return cmd
}
// runSearch runs the search command.
func runSearch(
cmd *c.Command,
args []string,
output *string,
) error {
if len(args) == 0 {
o := formatter.Yellow("โก No keywords provided...")
*output = o
return nil
}
historyRepo := repository.NewHistoryRepository()
shuc := jrpApp.NewSearchHistoryUseCase(historyRepo)
shoDtos, err := shuc.Run(
cmd.Context(),
args,
searchOps.And,
searchOps.All,
searchOps.Favorited,
searchOps.Number,
)
if err != nil {
return err
}
if len(shoDtos) == 0 {
o := formatter.Yellow("โก No histories found...")
*output = o
return nil
}
f, err := formatter.NewFormatter(searchOps.Format)
if err != nil {
o := formatter.Red("โ Failed to create a formatter...")
*output = o
return err
}
o := f.Format(shoDtos)
*output = o
return nil
}
const (
// searchHelpTemplate is the help template of the search command.
searchHelpTemplate = `๐๐ Search the histories of the "generate" command.
You can search histories with keyword arguments.
Multiple keywords are separated by a space.
If you want to search histories by AND condition, you can use flag "-A" or "--and".
OR condition is by default.
You can specify how many histories to show with flag "-n" or "--number".
If you don't specify the number of histories, jrp will show the most recent 10 histories by default.
Also, you can show all histories by flag "-a" or "--all".
If you use the flag, the number flag will be ignored.
` + searchUsageTemplate
// searchUsageTemplate is the usage template of the search command.
searchUsageTemplate = `Usage:
jrp history search [flag] [arguments]
jrp history se [flag] [arguments]
jrp history S [flag] [arguments]
Flags:
-A, --and ๐ง search histories by AND condition
-n, --number ๐ข number how many histories to show (default 10, e.g: 50)
-a, --all ๐ show all histories
-F, --favorited ๐ show only favorited histories
-f, --format ๐ format of the output (default "table", e.g: "plain")
-h, --help ๐ค help for search
Arguments:
keywords ๐ก search histories by keywords (multiple keywords are separated by space)
`
)
package history
import (
"strconv"
c "github.com/spf13/cobra"
jrpApp "github.com/yanosea/jrp/v2/app/application/jrp"
"github.com/yanosea/jrp/v2/app/infrastructure/jrp/repository"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/formatter"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// ShowOptions provides the options for the show command.
type ShowOptions struct {
// Number is a flag to specify the number of histories to show.
Number int
// All is a flag to show all the histories.
All bool
// Favorited is a flag to show only favorited histories.
Favorited bool
// Format is a flag to specify the format of the output.
Format string
}
var (
// showOps is a variable to store the show options with the default values for injecting the dependencies in testing.
showOps = ShowOptions{
Number: 1,
All: false,
Favorited: false,
Format: "table",
}
)
// NewShowCommand returns a new instance of the show command.
func NewShowCommand(
cobra proxy.Cobra,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("show")
cmd.SetAliases([]string{"sh", "s"})
cmd.SetUsageTemplate(showUsageTemplate)
cmd.SetHelpTemplate(showHelpTemplate)
cmd.SetArgs(cobra.MaximumNArgs(1))
cmd.SetSilenceErrors(true)
cmd.Flags().IntVarP(
&showOps.Number,
"number",
"n",
10,
"๐ข number how many histories to show (default 10, e.g. : 50)",
)
cmd.Flags().BoolVarP(
&showOps.All,
"all",
"a",
false,
"๐ show all the history",
)
cmd.Flags().BoolVarP(
&showOps.Favorited,
"favorited",
"F",
false,
"๐ show only favorited histories",
)
cmd.Flags().StringVarP(
&showOps.Format,
"format",
"f",
"table",
"๐ format of the output (default \"table\", e.g. : \"plain\")",
)
cmd.SetRunE(
func(cmd *c.Command, args []string) error {
return runShow(
cmd,
args,
output,
)
},
)
return cmd
}
// runShow runs the show command.
func runShow(
cmd *c.Command,
args []string,
output *string,
) error {
var number = showOps.Number
isDefaultNumber := number == 10
if len(args) > 0 {
argNumber, err := strconv.Atoi(args[0])
if err != nil {
o := formatter.Red("๐จ The number argument must be an integer...")
*output = o
return err
}
if isDefaultNumber {
number = argNumber
} else {
if argNumber > number {
number = argNumber
}
}
}
historyRepo := repository.NewHistoryRepository()
ghuc := jrpApp.NewGetHistoryUseCase(historyRepo)
ghoDtos, err := ghuc.Run(
cmd.Context(),
showOps.All,
showOps.Favorited,
number,
)
if err != nil {
return err
}
if len(ghoDtos) == 0 {
o := formatter.Yellow("โก No histories found...")
*output = o
return nil
}
f, err := formatter.NewFormatter(showOps.Format)
if err != nil {
o := formatter.Red("โ Failed to create a formatter...")
*output = o
return err
}
o := f.Format(ghoDtos)
*output = o
return nil
}
const (
// showHelpTemplate is the help template of the show command.
showHelpTemplate = `๐๐ Show the histories of the "generate" command.
You can specify how many histories to show by flag "-n" or "--number" or a number argument.
jrp will get the most recent histories from the histories.
If you don't specify the number of histories, jrp will show the most recent 10 histories by default.
If both are provided, the larger number takes precedence.
Also, you can show all the histories by flag "-a" or "--all".
If you use the flag, the number flag or argument will be ignored.
` + showUsageTemplate
// showUsageTemplate is the usage template of the show command.
showUsageTemplate = `Usage:
jrp history show [flag] [argument]
jrp history sh [flag] [argument]
jrp history s [flag] [argument]
Flags:
-n, --number ๐ข number how many histories to show (default 10, e.g. : 50)
-a, --all ๐ show all the histories
-F, --favorited ๐ show only favorited histories
-f, --format ๐ format of the output (default "table", e.g. : "plain")
-h, --help ๐ค help for show
Argument:
number ๐ข number how many histories to show (default 10, e.g. : 50)
`
)
package jrp
import (
"strconv"
c "github.com/spf13/cobra"
jrpApp "github.com/yanosea/jrp/v2/app/application/jrp"
"github.com/yanosea/jrp/v2/app/infrastructure/jrp/repository"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/formatter"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/presenter"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// UnfavoriteOptions provides the options for the unfavorite command.
type UnfavoriteOptions struct {
// All is a flag to unfavorite all the histories.
All bool
// NoConfirm is a flag to not confirm before unfavoriting all the historyies.
NoConfirm bool
}
var (
// unfavoriteOps is a variable to store the unfavorite options with the default values for injecting the dependencies in testing.
unfavoriteOps = UnfavoriteOptions{
All: false,
NoConfirm: false,
}
)
// NewUnfavoriteCommand returns a new instance of the unfavorite command.
func NewUnfavoriteCommand(
cobra proxy.Cobra,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("unfavorite")
cmd.SetAliases([]string{"unf", "u"})
cmd.SetUsageTemplate(unfavoriteUsageTemplate)
cmd.SetHelpTemplate(unfavoriteHelpTemplatep)
cmd.SetSilenceErrors(true)
cmd.Flags().BoolVarP(
&unfavoriteOps.All,
"all",
"a",
false,
"โจ remove all favorited phrases",
)
cmd.Flags().BoolVarP(
&unfavoriteOps.NoConfirm,
"no-confirm",
"",
false,
"๐ซ do not confirm before removing all the favorited phrases",
)
cmd.SetRunE(
func(cmd *c.Command, args []string) error {
return runUnfavorite(
cmd,
args,
output,
)
},
)
return cmd
}
// runUnfavorite runs the unfavorite command.
func runUnfavorite(
cmd *c.Command,
args []string,
output *string,
) error {
if len(args) == 0 && !unfavoriteOps.All {
o := formatter.Yellow("โก No ID arguments specified...")
*output = o
return nil
}
var ids []int
for _, arg := range args {
id, err := strconv.Atoi(arg)
if err != nil {
o := formatter.Red("๐จ The ID argument must be an integer...")
*output = o
return err
}
ids = append(ids, id)
}
historyRepo := repository.NewHistoryRepository()
uuc := jrpApp.NewUnfavoriteUseCase(historyRepo)
if unfavoriteOps.All && !unfavoriteOps.NoConfirm {
if answer, err := presenter.RunPrompt(
"Proceed with unfavoriting all the histories? [y/N]",
); err != nil {
return err
} else if answer != "y" && answer != "Y" {
o := formatter.Yellow("๐ซ Cancelled unfavoriting all the favorited histories.")
*output = o
return nil
}
}
if err := uuc.Run(
cmd.Context(),
ids,
unfavoriteOps.All,
); err != nil && err.Error() == "no favorited histories to unfavorite" {
o := formatter.Yellow("โก No favorited histories to unfavorite...")
*output = o
return nil
} else if err != nil {
return err
}
o := formatter.Green("โ
Unfavorited successfully!")
*output = o
return nil
}
const (
// unfavoriteHelpTemplatep is the help template of the unfavorite command.
unfavoriteHelpTemplatep = `โญ๐งน Unfavorite the favorited histories with the "favorite" command.
You can specify the favorited histories to unfavorite with ID arguments.
You have to get ID from the "history" command.
Multiple ID's can be specified separated by spaces.
This does not remove the history of the "generate" command, just unfavorite.
Also, you can unfavorite all the favorited histories with the "-a" or "--all" flag.
` + unfavoriteUsageTemplate
// unfavoriteUsageTemplate is the usage template of the unfavorite command.
unfavoriteUsageTemplate = `Usage:
jrp unfavorite [flag] [arguments]
jrp unf [flag] [arguments]
jrp u [flag] [arguments]
Flags:
-a, --all โจ unfavorite all the favorited histories
-no-confirm ๐ซ do not confirm before unfavoriting all the favorited histories
-h, --help ๐ค help for unfavorite
Arguments:
ID ๐ unfavorite with the the ID of the favorited history (e.g. : 1 2 3)
`
)
package jrp
import (
c "github.com/spf13/cobra"
jrpApp "github.com/yanosea/jrp/v2/app/application/jrp"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/formatter"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
var (
format = "plain"
)
// NewVersionCommand returns a new instance of the version command.
func NewVersionCommand(
cobra proxy.Cobra,
version string,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("version")
cmd.SetAliases([]string{"ver", "v"})
cmd.SetUsageTemplate(versionUsageTemplate)
cmd.SetHelpTemplate(versionHelpTemplate)
cmd.SetArgs(cobra.ExactArgs(0))
cmd.SetSilenceErrors(true)
cmd.SetRunE(
func(_ *c.Command, _ []string) error {
return runVersion(version, output)
},
)
return cmd
}
// runVersion runs the version command.
func runVersion(version string, output *string) error {
uc := jrpApp.NewGetVersionUseCase()
dto := uc.Run(version)
f, err := formatter.NewFormatter(format)
if err != nil {
o := formatter.Red("โ Failed to create a formatter...")
*output = o
return err
}
o := f.Format(dto)
*output = o
return nil
}
const (
// versionHelpTemplate is the help template of the version command.
versionHelpTemplate = `๐ Show the version of jrp
` + versionUsageTemplate
// versionUsageTemplate is the usage template of the version command.
versionUsageTemplate = `Usage:
jrp version [flags]
jrp ver [flags]
jrp v [flags]
Flags:
-h, --help ๐ค help for jrp version
`
)
package command
import (
c "github.com/spf13/cobra"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/command/jrp"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/command/jrp/completion"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/command/jrp/generate"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/command/jrp/history"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/config"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// RootOptions provides the options for the root command.
type RootOptions struct {
// Version is a flag to show the version of jrp.
Version bool
// GenerateOptions provides the options for the generate command.
GenerateOptions generate.GenerateOptions
}
var (
// rootOps is a variable to store the root options with the default values for injecting the dependencies in testing.
rootOps = RootOptions{
Version: false,
GenerateOptions: generate.GenerateOptions{
Number: 1,
Prefix: "",
Suffix: "",
DryRun: false,
Format: "table",
Interactive: false,
Timeout: 30,
},
}
)
// NewRootCommand returns a new instance of the root command.
func NewRootCommand(
cobra proxy.Cobra,
version string,
conf *config.JrpCliConfig,
output *string,
) proxy.Command {
cmd := cobra.NewCommand()
cmd.SetUse("jrp")
cmd.SetUsageTemplate(rootUsageTemplate)
cmd.SetHelpTemplate(rootHelpTemplate)
cmd.SetArgs(cobra.MaximumNArgs(1))
cmd.SetSilenceErrors(true)
cmd.PersistentFlags().BoolVarP(
&rootOps.Version,
"version",
"v",
false,
"๐ show the version of jrp",
)
cmd.Flags().IntVarP(
&rootOps.GenerateOptions.Number,
"number",
"n",
1,
"๐ข number of phrases to generate (default 1, e.g. : 10)",
)
cmd.Flags().StringVarP(
&rootOps.GenerateOptions.Prefix,
"prefix",
"p",
"",
"๐ก prefix of phrases to generate",
)
cmd.Flags().StringVarP(
&rootOps.GenerateOptions.Suffix,
"suffix",
"s",
"",
"๐ก suffix of phrases to generate",
)
cmd.Flags().BoolVarP(
&rootOps.GenerateOptions.DryRun,
"dry-run",
"d",
false,
"๐งช generate phrases without saving to the history",
)
cmd.Flags().StringVarP(
&rootOps.GenerateOptions.Format,
"format",
"f",
"table",
"๐ format of the output (default \"table\", e.g. : \"plain\")",
)
cmd.Flags().BoolVarP(
&rootOps.GenerateOptions.Interactive,
"interactive",
"i",
false,
"๐ฌ generate Japanese random phrases interactively",
)
cmd.Flags().IntVarP(
&rootOps.GenerateOptions.Timeout,
"timeout",
"t",
30,
"โ timeout in seconds for the interactive mode (default 30, e.g. : 10)",
)
interactiveCmd := generate.NewInteractiveCommand(
cobra,
output,
)
generateCmd := generate.NewGenerateCommand(
cobra,
interactiveCmd,
output,
)
versionCmd := jrp.NewVersionCommand(
cobra,
version,
output,
)
cmd.AddCommand(
completion.NewCompletionCommand(
cobra,
output,
),
jrp.NewDownloadCommand(
cobra,
conf,
output,
),
jrp.NewFavoriteCommand(
cobra,
output,
),
generateCmd,
history.NewHistoryCommand(
cobra,
output,
),
interactiveCmd,
jrp.NewUnfavoriteCommand(
cobra,
output,
),
versionCmd,
)
cmd.SetRunE(
func(cmd *c.Command, args []string) error {
return runRoot(cmd, args, generateCmd, versionCmd)
},
)
return cmd
}
// runRoot runs the root command.
func runRoot(
cmd *c.Command,
args []string,
generateCmd proxy.Command,
versionCmd proxy.Command,
) error {
if rootOps.Version {
return versionCmd.RunE(cmd, args)
}
generate.GenerateOps = rootOps.GenerateOptions
return generateCmd.RunE(cmd, args)
}
const (
// rootHelpTemplate is the help template of the root command.
rootHelpTemplate = `๐ฒ jrp is the CLI jokeey tool to generate Japanese random phrases.
You can generate Japanese random phrases.
You can specify how many phrases to generate by flag "-n" or "--number" or a number argument.
If both are provided, the larger number takes precedence.
And you can specify the prefix or suffix of the phrases to generate
by the flag "-p" or "--prefix" and "-s" or "--suffix".
Those commands below are the same.
"jrp" : "jrp generate"
"jrp interactive" : "jrp --interactive" : "jrp generate interactive" : "jrp generate --interactive"
` + rootUsageTemplate
// rootUsageTemplate is the usage template of the root command.
rootUsageTemplate = `Usage:
jrp [flags]
jrp [command]
jrp [argument]
Available Subcommands:
download, dl, d ๐ฆ Download WordNet Japan sqlite database file from the official web site.
generate, gen, g โจ Generate Japanese random phrases.
You can abbreviate "generate" sub command. ("jrp" and "jrp generate" are the same.)
interactive, int, i ๐ฌ Generate Japanese random phrases interactively.
history, hist, h ๐ Manage the histories of the "generate" command.
favorite, fav, f โญ Favorite the histories of the "generate" command.
unfavorite, unf, u โ Unfavorite the favorited histories of the "generate" command.
completion comp, c ๐ง Generate the autocompletion script for the specified shell.
version ver, v ๐ Show the version of jrp.
help ๐ค Help for jrp.
Flags:
-n, --number ๐ข number of phrases to generate (default 1, e.g. : 10)
-p, --prefix ๐ก prefix of phrases to generate
-s, --suffix ๐ก suffix of phrases to generate
-d, --dry-run ๐งช generate phrases without saving as the histories
-f, --format ๐ format of the output (default "table", e.g. : "plain")
-i, --interactive ๐ฌ generate Japanese random phrases interactively
-t, --timeout โ timeout in seconds for the interactive mode (default 30, e.g. : 10)
-h, --help ๐ค help for jrp
-v, --version ๐ version for jrp
Argument:
number ๐ข number of phrases to generate (e.g. : 10)
Use "jrp [command] --help" for more information about a command.
`
)
package config
import (
"path/filepath"
"strings"
baseConfig "github.com/yanosea/jrp/v2/app/config"
"github.com/yanosea/jrp/v2/app/infrastructure/database"
"github.com/yanosea/jrp/v2/pkg/proxy"
"github.com/yanosea/jrp/v2/pkg/utility"
)
// JrpCliConfigurator is an interface that gets the configuration of the Jrp cli application.
type JrpCliConfigurator interface {
GetConfig() (*JrpCliConfig, error)
}
// cliConfigurator is a struct that implements the JrpCliConfigurator interface.
type cliConfigurator struct {
*baseConfig.BaseConfigurator
}
// NewJrpCliConfigurator creates a new JrpCliConfigurator.
func NewJrpCliConfigurator(
envconfigProxy proxy.Envconfig,
fileUtil utility.FileUtil,
) JrpCliConfigurator {
return &cliConfigurator{
BaseConfigurator: baseConfig.NewConfigurator(
envconfigProxy,
fileUtil,
),
}
}
// JrpCliConfig is a struct that contains the configuration of the Jrp cli application.
type JrpCliConfig struct {
baseConfig.JrpConfig
JrpDBType database.DBType
JrpDBDsn string
}
// envConfig is a struct that contains the environment variables.
type envConfig struct {
JrpDBType database.DBType `envconfig:"JRP_DB_TYPE" default:"sqlite"`
JrpDBDsn string `envconfig:"JRP_DB" default:"XDG_DATA_HOME/jrp/jrp.db"`
WnJpnDBType database.DBType `envconfig:"JRP_WNJPN_DB_TYPE" default:"sqlite"`
WnJpnDBDsn string `envconfig:"JRP_WNJPN_DB" default:"XDG_DATA_HOME/jrp/wnjpn.db"`
}
// GetConfig gets the configuration of the Jrp cli application.
func (c *cliConfigurator) GetConfig() (*JrpCliConfig, error) {
var env envConfig
if err := c.Envconfig.Process("", &env); err != nil {
return nil, err
}
config := &JrpCliConfig{
JrpConfig: baseConfig.JrpConfig{
WNJpnDBType: env.WnJpnDBType,
WNJpnDBDsn: env.WnJpnDBDsn,
},
JrpDBType: env.JrpDBType,
JrpDBDsn: env.JrpDBDsn,
}
if config.JrpDBType == database.SQLite || config.WNJpnDBType == database.SQLite {
xdgDataHome, err := c.FileUtil.GetXDGDataHome()
if err != nil {
return nil, err
}
if config.JrpDBType == database.SQLite {
config.JrpDBDsn = strings.Replace(
config.JrpDBDsn,
"XDG_DATA_HOME",
xdgDataHome,
1,
)
if err := c.FileUtil.MkdirIfNotExist(
filepath.Dir(config.JrpDBDsn),
); err != nil {
return nil, err
}
}
if config.WNJpnDBType == database.SQLite {
config.WNJpnDBDsn = strings.Replace(
config.WNJpnDBDsn,
"XDG_DATA_HOME",
xdgDataHome,
1,
)
if err := c.FileUtil.MkdirIfNotExist(
filepath.Dir(config.WNJpnDBDsn),
); err != nil {
return nil, err
}
}
}
return config, nil
}
package formatter
import (
"errors"
"fmt"
)
// Formatter is an interface that formats the output of jrp cli.
type Formatter interface {
Format(result interface{}) string
}
// NewFormatter returns a new instance of the Formatter interface.
func NewFormatter(
format string,
) (Formatter, error) {
var f Formatter
switch format {
case "plain":
f = NewPlainFormatter()
case "table":
f = NewTableFormatter()
default:
return nil, errors.New("invalid format")
}
return f, nil
}
// AppendErrorToOutput appends an error to the output.
func AppendErrorToOutput(err error, output string) string {
if err == nil && output == "" {
return ""
}
var result string
if err != nil {
if output == "" {
result = fmt.Sprintf("Error : %s", err)
} else {
result = fmt.Sprintf(output+"\nError : %s", err)
}
} else {
result = output
}
if result != "" {
result = Red(result)
}
return result
}
package formatter
import (
"fmt"
jrpApp "github.com/yanosea/jrp/v2/app/application/jrp"
)
// PlainFormatter is a struct that formats the output of jrp cli.
type PlainFormatter struct{}
// NewPlainFormatter returns a new instance of the PlainFormatter struct.
func NewPlainFormatter() *PlainFormatter {
return &PlainFormatter{}
}
// Format formats the output of jrp cli.
func (f *PlainFormatter) Format(result interface{}) string {
var formatted string
switch v := result.(type) {
case *jrpApp.GetVersionUseCaseOutputDto:
formatted = fmt.Sprintf("jrp version %s", v.Version)
case []*jrpApp.GenerateJrpUseCaseOutputDto:
for i, item := range v {
formatted += item.Phrase
if i < len(v)-1 {
formatted += "\n"
}
}
case []*jrpApp.GetHistoryUseCaseOutputDto:
for i, item := range v {
formatted += item.Phrase
if i < len(v)-1 {
formatted += "\n"
}
}
case []*jrpApp.SearchHistoryUseCaseOutputDto:
for i, item := range v {
formatted += item.Phrase
if i < len(v)-1 {
formatted += "\n"
}
}
default:
formatted = ""
}
return formatted
}
package formatter
import (
"fmt"
"slices"
"strconv"
"strings"
"time"
jrpApp "github.com/yanosea/jrp/v2/app/application/jrp"
"github.com/yanosea/jrp/v2/pkg/proxy"
"github.com/yanosea/jrp/v2/pkg/utility"
)
// TableFormatter is a struct that formats the output of jrp cli.
type TableFormatter struct{}
// NewTableFormatter returns a new instance of the TableFormatter struct.
func NewTableFormatter() *TableFormatter {
return &TableFormatter{}
}
var (
// t is a variable to store the table writer with the default values for injecting the dependencies in testing.
t = utility.NewTableWriterUtil(proxy.NewTableWriter())
)
// tableData is a struct that holds the data of a table.
type tableData struct {
header []string
rows [][]string
}
// Format formats the output of jrp cli.
func (f *TableFormatter) Format(result interface{}) string {
var data tableData
switch v := result.(type) {
case []*jrpApp.GenerateJrpUseCaseOutputDto:
data = f.formatGenerateJrp(v)
case []*jrpApp.GetHistoryUseCaseOutputDto:
data = f.formatHistory(v, func(h interface{}) (int, string, string, string, int, time.Time, time.Time) {
dto := h.(*jrpApp.GetHistoryUseCaseOutputDto)
return dto.ID, dto.Phrase, dto.Prefix, dto.Suffix, dto.IsFavorited, dto.CreatedAt, dto.UpdatedAt
})
case []*jrpApp.SearchHistoryUseCaseOutputDto:
data = f.formatHistory(v, func(h interface{}) (int, string, string, string, int, time.Time, time.Time) {
dto := h.(*jrpApp.SearchHistoryUseCaseOutputDto)
return dto.ID, dto.Phrase, dto.Prefix, dto.Suffix, dto.IsFavorited, dto.CreatedAt, dto.UpdatedAt
})
default:
return ""
}
return f.getTableString(data)
}
// formatGenerateJrp formats the output of the GenerateJrp use case.
func (f *TableFormatter) formatGenerateJrp(items []*jrpApp.GenerateJrpUseCaseOutputDto) tableData {
header := []string{"phrase", "prefix", "suffix", "created_at"}
noId := slices.ContainsFunc(items, func(dto *jrpApp.GenerateJrpUseCaseOutputDto) bool {
return dto.ID == 0
})
if !noId {
header = append([]string{"id"}, header...)
}
var rows [][]string
for _, jrp := range items {
row := []string{jrp.Phrase, jrp.Prefix, jrp.Suffix, jrp.CreatedAt.Format("2006-01-02 15:04:05")}
if !noId {
row = append([]string{strconv.Itoa(jrp.ID)}, row...)
}
rows = append(rows, row)
}
if !noId {
rows = f.addTotalRow(rows)
}
return tableData{header: header, rows: rows}
}
// formatHistory formats the output of the GetHistory and SearchHistory use cases.
func (f *TableFormatter) formatHistory(items interface{}, getData func(interface{}) (int, string, string, string, int, time.Time, time.Time)) tableData {
header := []string{"id", "phrase", "prefix", "suffix", "is_favorited", "created_at", "updated_at"}
var rows [][]string
addRows := func(v interface{}) {
switch v := v.(type) {
case []*jrpApp.GetHistoryUseCaseOutputDto:
for _, item := range v {
id, phrase, prefix, suffix, isFavorited, createdAt, updatedAt := getData(item)
favorited := ""
if isFavorited == 1 {
favorited = "โ"
}
rows = append(rows, []string{
strconv.Itoa(id),
phrase,
prefix,
suffix,
favorited,
createdAt.Format("2006-01-02 15:04:05"),
updatedAt.Format("2006-01-02 15:04:05"),
})
}
case []*jrpApp.SearchHistoryUseCaseOutputDto:
for _, item := range v {
id, phrase, prefix, suffix, isFavorited, createdAt, updatedAt := getData(item)
favorited := ""
if isFavorited == 1 {
favorited = "โ"
}
rows = append(rows, []string{
strconv.Itoa(id),
phrase,
prefix,
suffix,
favorited,
createdAt.Format("2006-01-02 15:04:05"),
updatedAt.Format("2006-01-02 15:04:05"),
})
}
default:
return
}
}
addRows(items)
if len(rows) <= 0 {
return tableData{}
}
rows = f.addTotalRow(rows)
return tableData{header: header, rows: rows}
}
// addTotalRow adds a total row to the table.
func (f *TableFormatter) addTotalRow(rows [][]string) [][]string {
if len(rows) == 0 {
return [][]string{}
}
emptyRow := make([]string, len(rows[0]))
for i := range emptyRow {
emptyRow[i] = ""
}
totalRow := make([]string, len(rows[0]))
totalRow[0] = fmt.Sprintf("TOTAL : %d jrps!", len(rows))
rows = append(rows, emptyRow)
rows = append(rows, totalRow)
return rows
}
// getTableString returns a string representation of a table.
func (f *TableFormatter) getTableString(data tableData) string {
if len(data.header) == 0 || len(data.rows) == 0 {
return ""
}
tableString := &strings.Builder{}
table := t.GetNewDefaultTable(tableString)
table.SetHeader(data.header)
table.AppendBulk(data.rows)
table.Render()
return strings.TrimSuffix(tableString.String(), "\n")
}
package main
import (
"context"
"os"
"github.com/yanosea/jrp/v2/app/presentation/cli/jrp/command"
"github.com/yanosea/jrp/v2/pkg/proxy"
"github.com/yanosea/jrp/v2/pkg/utility"
)
// JrpCliParams is a struct that represents the options of jrp cli.
type JrpCliParams struct {
// Version is the version of jrp cli.
Version string
// Cobra is a proxy of spf13/cobra.
Cobra proxy.Cobra
// Envconfig is a proxy of kelseyhightower/envconfig.
Envconfig proxy.Envconfig
// Sql is a proxy of database/sql.
Sql proxy.Sql
// FileUtil provides the file utility.
FileUtil utility.FileUtil
// VersionUtil provides the version of the application.
VersionUtil utility.VersionUtil
}
var (
// version is the version of jrp cli and is embedded by goreleaser.
version = ""
// exit is a variable that contains the os.Exit function for injecting dependencies in testing.
exit = os.Exit
// jrpCliParams is a variable that contains the JrpCliParams struct.
jrpCliParams = JrpCliParams{
Version: version,
Cobra: proxy.NewCobra(),
Envconfig: proxy.NewEnvconfig(),
Sql: proxy.NewSql(),
FileUtil: utility.NewFileUtil(
proxy.NewGzip(),
proxy.NewIo(),
proxy.NewOs(),
),
VersionUtil: utility.NewVersionUtil(proxy.NewDebug()),
}
)
// main is the entry point of jrp cli.
func main() {
cli := command.NewCli(
jrpCliParams.Cobra,
)
if exitCode := cli.Init(
jrpCliParams.Envconfig,
jrpCliParams.Sql,
jrpCliParams.Version,
jrpCliParams.FileUtil,
jrpCliParams.VersionUtil,
); exitCode != 0 {
exit(exitCode)
}
ctx := context.Background()
defer ctx.Done()
exit(cli.Run(ctx))
}
package presenter
import (
"github.com/yanosea/jrp/v2/pkg/proxy"
"github.com/yanosea/jrp/v2/pkg/utility"
)
var (
// Ku is a variable that contains the KeyboardUtil struct for injecting dependencies in testing.
Ku = utility.NewKeyboardUtil(proxy.NewKeyboard())
)
// CloseKeyboard closes the keyboard.
func CloseKeyboard() error {
return Ku.CloseKeyboard()
}
// GetKey gets a key from the keyboard.
func GetKey(timeoutSec int) (string, error) {
return Ku.GetKey(timeoutSec)
}
// OpenKeyboard opens the keyboard.
func OpenKeyboard() error {
return Ku.OpenKeyboard()
}
package presenter
import (
"fmt"
"io"
)
// PrintFunc is a function type that defines the signature for printing output.
type PrintFunc func(writer io.Writer, output string) error
// Print is a function that writes the output to the writer.
var Print PrintFunc = func(writer io.Writer, output string) error {
if output != "" && output != "\n" {
_, err := fmt.Fprintf(writer, "%s\n", output)
return err
} else {
_, err := fmt.Fprintln(writer)
return err
}
}
package presenter
import (
"github.com/yanosea/jrp/v2/pkg/proxy"
"github.com/yanosea/jrp/v2/pkg/utility"
)
var (
// Pu is a variable that contains the PromptUtil struct for injecting dependencies in testing.
Pu = utility.NewPromptUtil(proxy.NewPromptui())
)
// RunPrompt runs the prompt.
func RunPrompt(label string) (string, error) {
prompt := Pu.GetPrompt(label)
return prompt.Run()
}
package presenter
import (
"github.com/yanosea/jrp/v2/pkg/proxy"
"github.com/yanosea/jrp/v2/pkg/utility"
)
var (
// Su is a variable that contains the SpinnerUtil struct for injecting dependencies in testing.
Su = utility.NewSpinnerUtil(proxy.NewSpinners())
// spinner is a variable that contains the Spinner struct.
spinner proxy.Spinner
)
// StartSpinner gets and starts the spinner.
func StartSpinner(isRversed bool, color string, suffix string) error {
sp, err := Su.GetSpinner(isRversed, color, suffix)
if err != nil {
return err
}
spinner = sp
sp.Start()
return nil
}
// StopSpinner stops the spinner.
func StopSpinner() {
if spinner != nil {
spinner.Stop()
}
}
package utility
import (
"os"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// Capturer is an interface that captures the output of a function.
type Capturer interface {
CaptureOutput(fnc func()) (string, string, error)
}
// capturer is a struct that implements the Captures interface.
type capturer struct {
// os is an interface for operating system functions.
os proxy.Os
// stdBuffer is a buffer for standard output.
stdBuffer proxy.Buffer
// errBuffer is a buffer for error output.
errBuffer proxy.Buffer
}
// NewCapturer returns a new instance of the capturer struct.
func NewCapturer(
os proxy.Os,
stdBuffer proxy.Buffer,
errBuffer proxy.Buffer,
) *capturer {
return &capturer{
os: os,
stdBuffer: stdBuffer,
errBuffer: errBuffer,
}
}
// CaptureOutput captures the output of a function.
func (c *capturer) CaptureOutput(fnc func()) (string, string, error) {
origStdout := os.Stdout
origStderr := os.Stderr
defer func() {
os.Stdout = origStdout
os.Stderr = origStderr
}()
rOut, wOut, err := c.os.Pipe()
if err != nil {
return "", "", err
}
rErr, wErr, err := c.os.Pipe()
if err != nil {
return "", "", err
}
os.Stdout = wOut.(interface{ AsOsFile() *os.File }).AsOsFile()
os.Stderr = wErr.(interface{ AsOsFile() *os.File }).AsOsFile()
fnc()
if err := wOut.Close(); err != nil {
return "", "", err
}
if err := wErr.Close(); err != nil {
return "", "", err
}
if _, err := c.stdBuffer.ReadFrom(rOut); err != nil {
return "", "", err
}
if _, err := c.errBuffer.ReadFrom(rErr); err != nil {
return "", "", err
}
stdout := c.stdBuffer.String()
errout := c.errBuffer.String()
c.stdBuffer.Reset()
c.errBuffer.Reset()
return stdout, errout, nil
}
package utility
import (
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// DownloadUtil is an interface that contains the utility functions for downloading files.
type DownloadUtil interface {
Download(url string) (proxy.Response, error)
}
// downloadUtil is a struct that contains the utility functions for downloading files.
type downloadUtil struct {
http proxy.Http
}
// NewDownloadUtil returns a new instance of the DownloadUtil struct.
func NewDownloadUtil(http proxy.Http) DownloadUtil {
return &downloadUtil{
http: http,
}
}
// Download downloads a file from the given URL.
func (d *downloadUtil) Download(url string) (proxy.Response, error) {
if res, err := d.http.Get(url); err != nil {
return nil, err
} else {
return res, nil
}
}
package utility
import (
"io"
"path/filepath"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// FileUtil is an interface that contains the utility functions for file operations.
type FileUtil interface {
ExtractGzFile(gzFilePath, destDir string) error
GetXDGDataHome() (string, error)
HideFile(filePath string) (string, error)
IsExist(name string) bool
MkdirIfNotExist(dirPath string) error
RemoveAll(path string) error
SaveToTempFile(body io.Reader, fileName string) (string, error)
UnhideFile(filePath string) error
}
// fileUtil is a struct that contains the utility functions for file operations.
type fileUtil struct {
gzip proxy.Gzip
io proxy.Io
os proxy.Os
}
// NewFileUtil returns a new instance of the FileUtil struct.
func NewFileUtil(
gzip proxy.Gzip,
io proxy.Io,
os proxy.Os,
) FileUtil {
return &fileUtil{
gzip: gzip,
io: io,
os: os,
}
}
// ExtractGzFile extracts a gzipped file to the destination directory.
func (f *fileUtil) ExtractGzFile(gzFilePath, destFilePath string) error {
var deferErr error
gzFile, err := f.os.Open(gzFilePath)
if err != nil {
return err
}
defer func() {
deferErr = gzFile.Close()
}()
gzReader, err := f.gzip.NewReader(gzFile)
if err != nil {
return err
}
defer func() {
deferErr = gzReader.Close()
}()
destFile, err := f.os.Create(destFilePath)
if err != nil {
return err
}
defer func() {
deferErr = destFile.Close()
}()
if _, err := f.io.Copy(destFile, gzReader); err != nil {
return err
}
return deferErr
}
// GetXDGDataHome returns the XDG data home directory.
func (f *fileUtil) GetXDGDataHome() (string, error) {
xdgDataHome := f.os.Getenv("XDG_DATA_HOME")
if xdgDataHome == "" {
homeDir, err := f.os.UserHomeDir()
if err != nil {
return "", err
}
xdgDataHome = filepath.Join(homeDir, ".local", "share")
}
return xdgDataHome, nil
}
// HideFile hides the file by adding a dot prefix to the file name.
func (f *fileUtil) HideFile(filePath string) (string, error) {
hiddenFilePath := filepath.Join(filepath.Dir(filePath), "."+filepath.Base(filePath))
if err := f.os.Rename(filePath, hiddenFilePath); err != nil {
return "", err
}
return hiddenFilePath, nil
}
// IsExist checks if the file or directory exists.
func (f *fileUtil) IsExist(name string) bool {
_, err := f.os.Stat(name)
return !f.os.IsNotExist(err)
}
// MkdirIfNotExist creates a directory if it does not exist.
func (f *fileUtil) MkdirIfNotExist(dirPath string) error {
if _, err := f.os.Stat(dirPath); f.os.IsNotExist(err) {
if err := f.os.MkdirAll(dirPath, 0755); err != nil {
return err
}
}
return nil
}
// RemoveAll removes path and any children it contains.
func (f *fileUtil) RemoveAll(path string) error {
return f.os.RemoveAll(path)
}
// SaveToTempFile saves the body to a temporary file.
func (f *fileUtil) SaveToTempFile(body io.Reader, fileName string) (string, error) {
var deferErr error
tempFilePath := filepath.Join(f.os.TempDir(), fileName)
file, err := f.os.Create(tempFilePath)
if err != nil {
return "", err
}
defer func() {
deferErr = file.Close()
}()
if _, err := f.io.Copy(file, body); err != nil {
return "", err
}
return tempFilePath, deferErr
}
// UnhideFile unhides the file by removing the dot prefix from the file name.
func (f *fileUtil) UnhideFile(hiddenFilePath string) error {
filePath := filepath.Join(filepath.Dir(hiddenFilePath), filepath.Base(hiddenFilePath)[1:])
if err := f.os.Rename(hiddenFilePath, filePath); err != nil {
return err
}
return nil
}
package utility
import (
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// JsonUtil is an interface that contains the utility functions for JSON.
type JsonUtil interface {
Marshal(v interface{}) ([]byte, error)
}
// jsonUtil is a struct that contains the utility functions for JSON.
type jsonUtil struct {
json proxy.Json
}
// NewJsonUtil returns a new instance of the JsonUtil struct.
func NewJsonUtil(json proxy.Json) JsonUtil {
return &jsonUtil{
json: json,
}
}
// Marshal marshals v into JSON.
func (ju *jsonUtil) Marshal(v interface{}) ([]byte, error) {
return ju.json.Marshal(v)
}
package utility
import (
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// KeyboardUtil provides the utility for the keyboard.
type KeyboardUtil interface {
CloseKeyboard() error
GetKey(timeoutSec int) (string, error)
OpenKeyboard() error
}
// keyboardUtil is a struct that implements the KeyboardUtil interface.
type keyboardUtil struct {
keyboard proxy.Keyboard
}
// NewKeyboardUtil returns a new instance of the KeyboardUtil.
func NewKeyboardUtil(
keyboard proxy.Keyboard,
) KeyboardUtil {
return &keyboardUtil{
keyboard: keyboard,
}
}
// CloseKeyboard closes the keyboard.
func (k *keyboardUtil) CloseKeyboard() error {
return k.keyboard.Close()
}
// GetKey returns a key from the keyboard.
func (k *keyboardUtil) GetKey(timeoutSec int) (string, error) {
rune, _, err := k.keyboard.GetKey(timeoutSec)
if err != nil {
return "", err
}
return string(rune), nil
}
// OpenKeyboard opens the keyboard.
func (k *keyboardUtil) OpenKeyboard() error {
return k.keyboard.Open()
}
package utility
import (
"github.com/yanosea/jrp/v2/pkg/proxy"
)
type PromptUtil interface {
GetPrompt(label string) proxy.Prompt
}
// promptUtil is a struct that implements the PromptUtil interface.
type promptUtil struct {
promptui proxy.Promptui
}
// NewPromptUtil returns a new instance of the promptUtil struct.
func NewPromptUtil(
promptui proxy.Promptui,
) PromptUtil {
return &promptUtil{
promptui: promptui,
}
}
// GetPrompt returns a new instance of the promptui.Prompt.
func (p *promptUtil) GetPrompt(label string) proxy.Prompt {
prompt := p.promptui.NewPrompt()
prompt.SetLabel(label)
return prompt
}
package utility
import (
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// RandUtil is an interface that contains the utility functions for generating random numbers.
type RandUtil interface {
GenerateRandomNumber(max int) int
}
// randUtil is a struct that contains the utility functions for generating random numbers.
type randUtil struct {
rand proxy.Rand
}
// NewRandUtil returns a new instance of the RandomUtil struct.
func NewRandUtil(rand proxy.Rand) RandUtil {
return &randUtil{
rand: rand,
}
}
// GenerateRandomNumber generates a random number between min and max.
func (ru *randUtil) GenerateRandomNumber(max int) int {
if max <= 0 {
return 0
}
return ru.rand.Intn(max)
}
package utility
import (
"github.com/yanosea/jrp/v2/pkg/proxy"
)
type SpinnerUtil interface {
GetSpinner(isReversed bool, color string, suffix string) (proxy.Spinner, error)
}
// spinnerUtil is a struct that implements the SpinnerUtil interface.
type spinnerUtil struct {
spinners proxy.Spinners
}
// NewSpinnerUtil returns a new instance of the spinnerUtil struct.
func NewSpinnerUtil(
spinners proxy.Spinners,
) SpinnerUtil {
return &spinnerUtil{
spinners: spinners,
}
}
// GetSpinner returns a new instance of the spinner.Spinner.
func (s *spinnerUtil) GetSpinner(isReversed bool, color string, suffix string) (proxy.Spinner, error) {
spinner := s.spinners.NewSpinner()
if isReversed {
spinner.Reverse()
}
if err := spinner.SetColor(color); err != nil {
return nil, err
}
spinner.SetSuffix(suffix)
return spinner, nil
}
package utility
import (
"strings"
)
// StringsUtil is an interface that contains the utility functions for manipulating strings.
type StringsUtil interface {
RemoveNewLines(s string) string
RemoveSpaces(s string) string
RemoveTabs(s string) string
}
// stringsUtil is a struct that contains the utility functions for manipulating strings.
type stringsUtil struct{}
// NewStringsUtil returns a new instance of the StringsUtil struct.
func NewStringsUtil() StringsUtil {
return &stringsUtil{}
}
// RemoveNewLines removes all new lines from the given strings.
func (s *stringsUtil) RemoveNewLines(str string) string {
return strings.ReplaceAll(str, "\n", "")
}
// RemoveSpaces removes all spaces from the given strings.
func (s *stringsUtil) RemoveSpaces(str string) string {
return strings.ReplaceAll(str, " ", "")
}
// RemoveTabs removes all tabs from the given strings.
func (s *stringsUtil) RemoveTabs(str string) string {
return strings.ReplaceAll(str, "\t", "")
}
package utility
import (
"io"
"github.com/olekukonko/tablewriter"
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// TableWriterUtil is an interface that contains the utility functions for writing tables.
type TableWriterUtil interface {
GetNewDefaultTable(writer io.Writer) proxy.Table
}
// tableWriterUtil is a struct that contains the utility functions for writing tables.
type tableWriterUtil struct {
tableWriter proxy.TableWriter
}
// NewTableWriterUtil returns a new instance of the TableWriterUtil interface.
func NewTableWriterUtil(tableWriter proxy.TableWriter) TableWriterUtil {
return &tableWriterUtil{
tableWriter: tableWriter,
}
}
// GetNewDefaultTable returns a new instance of the default table.
func (t *tableWriterUtil) GetNewDefaultTable(writer io.Writer) proxy.Table {
table := t.tableWriter.NewTable(writer)
table.SetAutoWrapText(false)
table.SetAutoFormatHeaders(true)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetTablePadding("\t")
table.SetNoWhiteSpace(true)
return table
}
package utility
import (
"github.com/yanosea/jrp/v2/pkg/proxy"
)
// VersionUtil is an interface that provides the version of the application.
type VersionUtil interface {
GetVersion(version string) string
}
// versionUtil is a struct that implements the VersionUtil interface.
type versionUtil struct {
debug proxy.Debug
}
// NewVersionUtil returns a new instance of the versionUtil struct.
func NewVersionUtil(
debug proxy.Debug,
) VersionUtil {
return &versionUtil{
debug: debug,
}
}
// GetVersion returns the version of the application.
func (v *versionUtil) GetVersion(version string) string {
// if version is embedded, return it.
if version != "" {
return version
}
if i, ok := v.debug.ReadBuildInfo(); !ok {
return "unknown"
} else if i.Main.Version != "" {
return i.Main.Version
} else {
return "dev"
}
}