diff --git a/README.md b/README.md index fdb4d00..f7aac3f 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Choose an item from a file or input stream using drand `cat /some/path/to/file | dchoose --count 2` or `dchoose --count 2 --file /some/path/to/file` +or +`echo -e "heads\ntails" | dchoose` ## Parameters * `-c, --count` diff --git a/index.js b/index.js index 8b99eac..d4889d2 100755 --- a/index.js +++ b/index.js @@ -7199,22 +7199,23 @@ function hashInput(input) { // src/main.ts async function main(params) { + printWinners(params, await draw(params)); +} +async function draw(params) { const { values, count, drandURL } = params; if (count === 0) { - process.exit(0); + return { round: 0, randomness: "", winners: [] }; } if (values.length <= count) { - printWinners(values); - process.exit(0); + return { round: 0, randomness: "", winners: values }; } - let randomness; if (params.randomness) { - randomness = params.randomness; - } else { - randomness = await fetchDrandRandomness(drandURL); + const winners2 = select(count, values, Buffer.from(params.randomness, "hex")); + return { round: 0, randomness: params.randomness, winners: winners2 }; } + const [round, randomness] = await fetchDrandRandomness(drandURL); const winners = select(count, values, Buffer.from(randomness, "hex")); - printWinners(winners); + return { round, randomness, winners }; } async function fetchDrandRandomness(drandURL) { const drandClient = new import_drand_client.HttpChainClient(new import_drand_client.HttpCachingChain(drandURL)); @@ -7224,12 +7225,16 @@ async function fetchDrandRandomness(drandURL) { if (beacon.round !== nextRound) { continue; } - return beacon.randomness; + return [nextRound, beacon.randomness]; } throw Error("this should never have happened"); } -function printWinners(winners) { - winners.forEach((winner) => console.log(winner)); +function printWinners(params, output2) { + if (!params.verbose) { + output2.winners.forEach((winner) => console.log(winner)); + } else { + console.log(JSON.stringify(output2)); + } } // src/params.ts @@ -7255,6 +7260,7 @@ function parseParams(opts) { count: Number.parseInt(opts.count), drandURL: opts.drandUrl, randomness: opts.randomness, + verbose: opts.verbose, values }; } @@ -7276,7 +7282,7 @@ function isValidURL(inputURL) { } // src/index.ts -program.option("-f,--file ", "a file you wish to use for selection; alternatively, you can pass options via stdin", "").option("-u,--drand-url ", "the URL you're using for drand randomness", "https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971").option("-c,--count ", "the number of items you wish to draw", "1").option("-r,--randomness ", "custom randomness, if you wish to repeat historical draws", ""); +program.option("-f,--file ", "a file you wish to use for selection; alternatively, you can pass options via stdin", "").option("-u,--drand-url ", "the URL you're using for drand randomness", "https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971").option("-c,--count ", "the number of items you wish to draw", "1").option("-r,--randomness ", "custom randomness, if you wish to repeat historical draws", "").option("-v,--verbose", "the tool will output more details about the draw than just the winners"); program.parse(process.argv); main(parseParamsAndExit(program.opts())); /*! Bundled license information: diff --git a/src/index.ts b/src/index.ts index f9108b8..4dbe5b7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ program .option("-u,--drand-url ", "the URL you're using for drand randomness", "https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971") .option("-c,--count ", "the number of items you wish to draw", "1") .option("-r,--randomness ", "custom randomness, if you wish to repeat historical draws", "") + .option("-v,--verbose", "the tool will output more details about the draw than just the winners") // including the arguments program.parse(process.argv) diff --git a/src/main.ts b/src/main.ts index 3cc585e..56d727c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,30 +6,40 @@ export type CLIParams = { drandURL: string randomness?: string values: Array + verbose: boolean } export async function main(params: CLIParams) { + printWinners(params, await draw(params)) +} + +type DrawResult = { + round: number + randomness: string + winners: Array +} + +export async function draw(params: CLIParams): Promise { const {values, count, drandURL} = params if (count === 0) { - process.exit(0) + return {round: 0, randomness: "", winners: []} } + if (values.length <= count) { - printWinners(values) - process.exit(0) + return {round: 0, randomness: "", winners: values} } - let randomness: string if (params.randomness) { - randomness = params.randomness - } else { - randomness = await fetchDrandRandomness(drandURL) + const winners = select(count, values, Buffer.from(params.randomness, "hex")) + return {round: 0, randomness: params.randomness, winners} } + const [round, randomness] = await fetchDrandRandomness(drandURL) const winners = select(count, values, Buffer.from(randomness, "hex")) - printWinners(winners) + return {round, randomness, winners} } -async function fetchDrandRandomness(drandURL: string): Promise { +async function fetchDrandRandomness(drandURL: string): Promise<[number, string]> { const drandClient = new HttpChainClient(new HttpCachingChain(drandURL)) const nextRound = roundAt(Date.now(), await drandClient.chain().info()) + 1 const abort = new AbortController() @@ -40,11 +50,15 @@ async function fetchDrandRandomness(drandURL: string): Promise { continue } - return beacon.randomness + return [nextRound, beacon.randomness] } throw Error("this should never have happened") } -function printWinners(winners: Array) { - winners.forEach(winner => console.log(winner)) +function printWinners(params: CLIParams, output: DrawResult) { + if (!params.verbose) { + output.winners.forEach(winner => console.log(winner)) + } else { + console.log(JSON.stringify(output)) + } } diff --git a/src/params.ts b/src/params.ts index cca8933..12e1863 100644 --- a/src/params.ts +++ b/src/params.ts @@ -30,6 +30,7 @@ export function parseParams(opts: OptionValues): CLIParams | string { count: Number.parseInt(opts.count), drandURL: opts.drandUrl, randomness: opts.randomness, + verbose: opts.verbose, values, } } diff --git a/test/main.test.ts b/test/main.test.ts index 842238f..5ec0be3 100644 --- a/test/main.test.ts +++ b/test/main.test.ts @@ -1,11 +1,77 @@ -import {test} from "@jest/globals" -import {main} from "../src/main" +import {describe, it, expect} from "@jest/globals" +import {draw, main} from "../src/main" -test("make sure it doesn't blow up", async () => { - const params = { - count: 1, - values: ["a", "b", "c"], - drandURL: "https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971" - } - await main(params) -}) \ No newline at end of file +describe("draws", () => { + describe("main function", () => { + it("verbose shouldn't blow up", async () => { + const params = { + count: 1, + values: ["a", "b", "c"], + drandURL: "https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971", + verbose: false, + } + await main(params) + }) + it("non-verbose shouldn't blow up", async () => { + const params = { + count: 1, + values: ["a", "b", "c"], + drandURL: "https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971", + verbose: false, + } + await main(params) + }) + }) + describe("draw function", () => { + it("should return no values for 0 count", async () => { + const params = { + count: 0, + values: ["a", "b", "c"], + drandURL: "https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971", + verbose: true, + } + const result = await draw(params) + expect(result.round).toEqual(0) + expect(result.winners).toEqual([]) + expect(result.randomness).toEqual("") + }) + it("should return all the values for a count less than the number of values", async () => { + const params = { + count: 5, + values: ["a", "b", "c"], + drandURL: "https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971", + verbose: true, + } + const result = await draw(params) + expect(result.round).toEqual(0) + expect(result.winners).toEqual(params.values) + expect(result.randomness).toEqual("") + }) + it("should return the same result each time for custom randomness", async () => { + const params = { + count: 1, + values: ["a", "b", "c"], + randomness: "5af934e9a82fcbc0f7d9cb7be197f6a9f74e10a49227f3dc72ed0686f7ab85f2", + drandURL: "https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971", + verbose: true, + } + const result = await draw(params) + expect(result.round).toEqual(0) + expect(result.randomness).toEqual(params.randomness) + + const result2 = await draw(params) + expect(result).toEqual(result2) + }) + it("should return a non-zero round for real randomness", async () => { + const params = { + count: 1, + values: ["a", "b", "c"], + drandURL: "https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971", + verbose: true, + } + const result = await draw(params) + expect(result.round).toBeGreaterThan(1) + expect(result.winners).toHaveLength(params.count) + }) + }) +})