Skip to content

Commit

Permalink
Merge branch '12-tags'
Browse files Browse the repository at this point in the history
  • Loading branch information
f3rno64 committed Feb 15, 2024
2 parents 09aaf9d + 34a8c1e commit 8f80e9e
Show file tree
Hide file tree
Showing 22 changed files with 281 additions and 108 deletions.
2 changes: 2 additions & 0 deletions src/color/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import clID from './id'
import clTag from './tag'
import clDate from './date'
import clText from './text'
import clError from './error'
Expand All @@ -9,6 +10,7 @@ import clHighlightRed from './highlight_red'

export {
clID,
clTag,
clText,
clDate,
clError,
Expand Down
5 changes: 5 additions & 0 deletions src/color/tag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import colors from 'colors'

const clTag = (tag: string): string => colors.bold.green.underline(tag)

export default clTag
9 changes: 2 additions & 7 deletions src/commands/breakdown/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { eachDayOfInterval, eachHourOfInterval } from 'date-fns'
import log from '../../log'
import { populateResults } from './utils'
import { printJustifiedContent } from '../../print'
import { type BreakdownCommandArgs } from './types'
import { type TimeSheet, type TimeSheetEntry } from '../../types'
import { type BreakdownCommandArgs, type BreakdownResults } from './types'
import {
clText,
clDate,
Expand All @@ -18,12 +19,6 @@ import {
clHighlightRed
} from '../../color'

import {
type TimeSheet,
type TimeSheetEntry,
type BreakdownResults
} from '../../types'

import {
getHourString,
getDurationLangString,
Expand Down
10 changes: 10 additions & 0 deletions src/commands/breakdown/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type Argv } from 'yargs'

import DB from '../../db'
import { TimeSheet, TimeSheetEntry } from '../../types/data'

export interface BreakdownCommandArgs {
db: DB
Expand All @@ -12,3 +13,12 @@ export interface BreakdownCommandArgs {
sheets?: string[]
humanize?: boolean
}

export interface BreakdownResult {
date: Date
duration: number
sheets: TimeSheet[]
entries: TimeSheetEntry[]
}

export type BreakdownResults = Record<string, BreakdownResult>
7 changes: 2 additions & 5 deletions src/commands/breakdown/utils/populate_results.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import _uniqBy from 'lodash/uniqBy'
import _isUndefined from 'lodash/isUndefined'

import {
type TimeSheet,
type TimeSheetEntry,
type BreakdownResults
} from '../../../types'
import { type BreakdownResults } from '../types'
import { type TimeSheet, type TimeSheetEntry } from '../../../types'

interface PopulateResultsArgs {
date: Date
Expand Down
10 changes: 6 additions & 4 deletions src/commands/in/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ import parseDate from 'time-speak'
import _isEmpty from 'lodash/isEmpty'
import _isUndefined from 'lodash/isUndefined'

import { printCheckedInEntry } from '../../print'
import { type InCommandArgs } from './types'
import { printCheckedInEntry } from '../../print'

const handler = async (args: InCommandArgs): Promise<void> => {
const { at, db, description, help, yargs } = args
const { yargs, db, help } = args

if (help) {
yargs.showHelp()
process.exit(0)
}

const finalDescription = description.join(' ')
const activeSheetName = db.getActiveSheetName()

if (activeSheetName === null) {
Expand All @@ -30,10 +29,13 @@ const handler = async (args: InCommandArgs): Promise<void> => {
throw new Error(`An entry is already active (${id}): ${entryDescription}`)
}

// TODO: Rename description to input or content (or think of a better name)
const { at, description: inputArray } = args
const input = inputArray.join(' ')
const startDate =
_isUndefined(at) || _isEmpty(at) ? new Date() : new Date(+parseDate(at))

const entry = await db.addActiveSheetEntry(name, finalDescription, startDate)
const entry = await db.addActiveSheetEntry({ sheet: name, input, startDate })

printCheckedInEntry(entry)
}
Expand Down
11 changes: 7 additions & 4 deletions src/commands/resume/handler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import log from '../../log'
import { type ResumeCommandArgs } from './types'
import { clHighlight, clSheet, clText } from '../../color'
import { clTag, clHighlight, clSheet, clText } from '../../color'

const handler = async (args: ResumeCommandArgs): Promise<void> => {
const { db, help, yargs } = args
Expand All @@ -12,18 +12,21 @@ const handler = async (args: ResumeCommandArgs): Promise<void> => {

const sheet = db.getActiveSheet()
const entry = db.getMostRecentlyActiveSheetEntry(sheet)
const { description, end, id } = entry
const { description, end, tags, id } = entry
const { name } = sheet
const tagsUI = (tags ?? []).map(clTag).join(' ')

if (end === null) {
throw new Error(
`Sheet ${name} already has an active entry (${id}: ${description})`
)
}

await db.addActiveSheetEntry(sheet, description)
await db.addActiveSheetEntry({ sheet, description, tags })

log(`${clSheet(`${name}:`)} ${clText('resumed')} ${clHighlight(description)}`)
log(
`${clSheet(`${name}:`)} ${clText('resumed')} ${clHighlight(description)} ${tagsUI}`
)
}

export default handler
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export const STORAGE_DIR_NAME = '.track-time-cli'
export const TEST_DB_PATH = path.join(__dirname, '../test-db.json')
export const STORAGE_PATH = path.join(os.homedir(), STORAGE_DIR_NAME)
export const DB_PATH = path.join(STORAGE_PATH, DB_FILE_NAME)
export const DB_VERSION = 2
44 changes: 44 additions & 0 deletions src/db/convert_json_db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { type TimeTrackerDB, type JSONTimeTrackerDB } from '../types'

const convertJSONDB = (jsonDB: JSONTimeTrackerDB): TimeTrackerDB => {
const {
sheets: jsonSheets,
version: jsonVersion,
activeSheetName: jsonActiveSheetName
} = jsonDB

const sheets = jsonSheets.map(
({
name: jsonName,
entries: jsonEntries,
activeEntryID: jsonActiveEntryID
}) => ({
name: jsonName,
activeEntryID: jsonActiveEntryID,
entries: jsonEntries.map(
({
id: jsonId,
start: jsonStart,
end: jsonEnd,
description: jsonDescription,
tags: jsonTags
}) => ({
id: jsonId,
description: jsonDescription,
tags: jsonTags,
start: new Date(jsonStart),
end: jsonEnd === null ? null : new Date(jsonEnd)
})
)
})
)

return {
version: jsonVersion,
activeSheetName: jsonActiveSheetName,
sheets
} as TimeTrackerDB
}

export default convertJSONDB
export { convertJSONDB }
100 changes: 70 additions & 30 deletions src/db.ts → src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,39 @@ import _isNumber from 'lodash/isNumber'
import _isString from 'lodash/isString'
import _isUndefined from 'lodash/isUndefined'

import * as U from './utils'
import { TEST_DB_PATH, DB_PATH, DEFAULT_SHEET_NAME } from './config'
import log from '../log'
import * as U from '../utils'
import * as CONFIG from '../config'
import convertJSONDB from './convert_json_db'
import migrateJSONDBToVersionTwo from './migrate_json_db_to_version_two'
import { TEST_DB_PATH, DB_PATH, DEFAULT_SHEET_NAME } from '../config'
import {
type TimeSheet,
type TimeTrackerDB,
type TimeSheetEntry
} from './types'
type TimeSheetEntry,
type JSONTimeTrackerDB
} from '../types'

const { DB_VERSION } = CONFIG
const { NODE_ENV } = process.env
const DEFAULT_DB_PATH = NODE_ENV === 'test' ? TEST_DB_PATH : DB_PATH

// TODO: Extract
interface AddActiveSheetEntryArgs {
sheet: TimeSheet | string
input?: string
description?: string
startDate?: Date
tags?: string[]
}

class DB {
db: TimeTrackerDB | null
dbPath: string

static genDB(): TimeTrackerDB {
return {
version: DB_VERSION,
sheets: [DB.genSheet(DEFAULT_SHEET_NAME)],
activeSheetName: DEFAULT_SHEET_NAME
} as TimeTrackerDB
Expand Down Expand Up @@ -54,14 +70,16 @@ class DB {
id: number,
description: string,
start?: Date,
end?: Date | null
) {
end?: Date | null,
tags?: string[]
): TimeSheetEntry {
return {
id,
description,
end: end ?? null,
start: start ?? new Date()
} as TimeSheetEntry
start: start ?? new Date(),
tags: tags ?? []
}
}

constructor(dbPath: string = DEFAULT_DB_PATH) {
Expand Down Expand Up @@ -104,33 +122,40 @@ class DB {

async load(): Promise<void> {
const dbPathDir = path.dirname(this.dbPath)

await U.ensureDirExists(dbPathDir)

const dbExists = await this.doesDBExist()

if (!dbExists) {
this.db = DB.genDB()

await this.save()
} else {
const dbJSON = await fs.readFile(this.dbPath, 'utf-8')
let db: TimeTrackerDB = {} as TimeTrackerDB
let jsonDB: JSONTimeTrackerDB = {} as JSONTimeTrackerDB

try {
db = JSON.parse(dbJSON)
jsonDB = JSON.parse(dbJSON)
} catch (err: any) {
throw new Error(`DB at ${this.dbPath} is invalid JSON: ${err}`)
}

db.sheets.forEach(({ entries }) => {
entries.forEach((entry: TimeSheetEntry) => {
entry.start = new Date(entry.start)
entry.end = entry.end === null ? null : new Date(entry.end)
})
})
let didMigrate = false

// TODO: Handle an arbitrary number of migrations between multiple
// versions.
if (jsonDB.version === 1 || _isUndefined(jsonDB.version)) {
jsonDB = migrateJSONDBToVersionTwo(jsonDB)
didMigrate = true

log('Migrated DB to version 2')
} else if (jsonDB.version > DB_VERSION || jsonDB.version < 1) {
throw new Error(`Unknown DB version ${jsonDB.version}, cannot load.`)
}

this.db = convertJSONDB(jsonDB)

this.db = db
if (didMigrate) {
await this.save()
}
}
}

Expand Down Expand Up @@ -390,25 +415,40 @@ class DB {
}

async addActiveSheetEntry(
sheet: TimeSheet | string,
description: string,
startDate?: Date
args: AddActiveSheetEntryArgs
): Promise<TimeSheetEntry> {
if (this.db === null) {
throw new Error('DB not loaded')
}

const {
description: descriptionArg,
sheet,
input,
tags: tagsArg,
startDate
} = args

const targetSheet = this._parseSheetArg(sheet)
const { entries } = targetSheet
const newEntryID = entries.length
const entry = DB.genSheetEntry(
newEntryID,
description,
startDate ?? new Date()
)
const id = entries.length
const start = startDate ?? new Date()
const end = null

let description = descriptionArg as string
let tags: string[] = tagsArg ?? []

if (_isEmpty(tags) && !_isUndefined(input)) {
const res = U.parseEntryFromInput(id, input, start)

tags = res.tags
description = res.description
}

const entry = DB.genSheetEntry(id, description, start, end, tags)

entries.push(entry)
targetSheet.activeEntryID = newEntryID
targetSheet.activeEntryID = id

await this.save()

Expand Down
Loading

0 comments on commit 8f80e9e

Please sign in to comment.