Skip to content

Commit

Permalink
feat: truncate history file (#249)
Browse files Browse the repository at this point in the history
* feat: truncate history file

* chore: husky

* chore: husky
  • Loading branch information
hongaar authored May 13, 2021
1 parent 4603e3e commit efee1a8
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 17 deletions.
1 change: 1 addition & 0 deletions .husky/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
_
4 changes: 4 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

yarn doc:toc && yarn doc:todos && git add .
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,8 @@ Creates a new program. Options (object, optional) can contain these keys:
displays program usage information.
- `version` (boolean, default: true) adds `version` and `--version` to the
program which displays program version from package.json.
- `historyFile` (string, defaults: {homedir}/.bandersnatch_history) is a path to
the app history file.
- `historyFile` (string | null, defaults: {homedir}/.bandersnatch_history) is a
path to the app history file. Set to NULL to disable.
#### `program.description(description)`
Expand Down Expand Up @@ -680,7 +680,6 @@ Optionally deploy to GitHub, S3, etc. using your preferred CD method if needed.
## Todo

- [ ] Better code coverage
- [ ] History file cleanup (retain first x lines only)
- [ ] Consider resolving ambiguity in _prompt_ param/method
- [ ] Async autocomplete method
- [ ] Choices autocompletion in REPL mode (open upstream PR in yargs)
Expand All @@ -694,6 +693,7 @@ Contributions are very welcome.
git clone [email protected]:hongaar/bandersnatch.git
cd bandersnatch
yarn
yarn husky install

# Run an example
yarn start examples/foo.ts
Expand Down
5 changes: 0 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,5 @@
"ts-jest": "26.5.6",
"ts-node": "9.1.1",
"typescript": "4.2.4"
},
"husky": {
"hooks": {
"pre-commit": "yarn doc:toc && yarn doc:todos"
}
}
}
14 changes: 14 additions & 0 deletions src/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import os from 'os'
import { REPLServer } from 'repl'
import { Program } from './program'

export const HISTSIZE = 500

/**
* Create new history instance.
*/
Expand All @@ -27,6 +29,18 @@ export class History {
entry = entry.join(' ')
}

// Truncate if needed and if possible
try {
const historyContents = fs.readFileSync(this.path, 'utf8').split(os.EOL)
if (historyContents.length > HISTSIZE) {
fs.writeFileSync(
this.path,
historyContents.slice(historyContents.length - HISTSIZE).join(os.EOL),
'utf8'
)
}
} catch (err) {}

fs.appendFileSync(this.path, entry + os.EOL)
}

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './argument'
export * from './autocompleter'
export * from './command'
export * from './history'
export * from './option'
export * from './program'
export * from './repl'
Expand Down
18 changes: 11 additions & 7 deletions src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ type ProgramOptions = {
version?: boolean

/**
* Use this history file in REPL mode.
* Use this history file. Set to NULL to disable history file.
*
* Defaults to `{homedir}/.bandersnatch_history`.
*/
historyFile?: string
historyFile?: string | null
}

/**
Expand All @@ -70,23 +70,25 @@ function extractCommandFromProcess() {

export class Program extends (EventEmitter as new () => TypedEventEmitter<Events>) {
private commands: Command<any>[] = []
private history: History
private history?: History
private replInstance?: Repl

constructor(public options: ProgramOptions = {}) {
super()

// Set default prompt
if (!this.options.prompt) {
if (typeof this.options.prompt === 'undefined') {
this.options.prompt = DEFAULT_PROMPT
}

// Set default historyFile
if (!this.options.historyFile) {
if (typeof this.options.historyFile === 'undefined') {
this.options.historyFile = path.join(os.homedir(), DEFAULT_HISTORY_FILE)
}

this.history = history(this)
if (this.options.historyFile !== null) {
this.history = history(this)
}
}

/**
Expand Down Expand Up @@ -228,7 +230,9 @@ export class Program extends (EventEmitter as new () => TypedEventEmitter<Events
})
)

this.replInstance.attachHistory(this.history)
if (this.history) {
this.replInstance.attachHistory(this.history)
}
this.replInstance.start()

return this.replInstance
Expand Down
3 changes: 1 addition & 2 deletions src/repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,9 @@ export class Repl {
ignoreUndefined: true,
})

// Setup history
// Setup history
this.history?.hydrateReplServer(this.server)


// Fixes bug with hidden cursor after enquirer prompt
// @ts-ignore
new Prompt().cursorShow()
Expand Down
62 changes: 62 additions & 0 deletions tests/history.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import fs from 'fs'
import os from 'os'
import path from 'path'
import { command, history, History, HISTSIZE, program } from '../src'

// https://stackoverflow.com/a/52560084/938297
function tmpFile(name = 'tmp_file', data = '', encoding = 'utf8') {
return new Promise<string>((resolve, reject) => {
const tempPath = path.join(os.tmpdir(), 'bandersnatch-')
fs.mkdtemp(tempPath, (err, folder) => {
if (err) return reject(err)

const file_name = path.join(folder, name)

fs.writeFile(file_name, data, encoding, (error_file) => {
if (error_file) return reject(error_file)

resolve(file_name)
})
})
})
}

test('history should return new History object', () => {
const app = program()

expect(history(app)).toBeInstanceOf(History)
})

test('commands should end up in history file', async () => {
const historyFile = await tmpFile('history_file')
const app = program({ historyFile }).add(command('test').action(() => {}))

await app.run('test')

expect(fs.readFileSync(historyFile, 'utf8')).toBe('test' + os.EOL)
})

test('history file should be truncated', async () => {
const historyFile = await tmpFile(
'history_file',
[...Array(HISTSIZE).keys()].join(os.EOL) + os.EOL
)
const app = program({ historyFile }).add(command('test').action(() => {}))

await app.run('test')

expect(fs.readFileSync(historyFile, 'utf8')).toBe(
[...Array(HISTSIZE).keys()].slice(1).join(os.EOL) + os.EOL + 'test' + os.EOL
)
})

test('commands should not end up in history file when disabled', async () => {
const historyFile = await tmpFile('history_file')
const app = program({ historyFile: null }).add(
command('test').action(() => {})
)

await app.run('test')

expect(fs.readFileSync(historyFile, 'utf8')).toBe('')
})

0 comments on commit efee1a8

Please sign in to comment.