Skip to content

Commit

Permalink
Merge pull request #89 from cabal-club/multi-cabal-protocol-version
Browse files Browse the repository at this point in the history
[RFC] Multiple Cabals Continuation

LETS DO THIS 🎉
  • Loading branch information
cblgh authored Nov 16, 2018
2 parents 70c2fa9 + c6b2a01 commit cf322c0
Show file tree
Hide file tree
Showing 8 changed files with 533 additions and 178 deletions.
4 changes: 4 additions & 0 deletions .cabal.yml-example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
cabals:
- cabal://bd45fde0ad866d4069af490f0ca9b07110808307872d4b659a4ff7a4ef85315a
- 22f7763be0e939160dd04137b66aaac8f2179350eec740e57a656dfdf1f4dc29
- cbl://3d45fde0ad866d4069af490f0ca9b07110808307872d4b659a4ff7a4ef853132
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ config.js
*.pdf
archives

# Cabal config files
.cabal.yml

#################
## Eclipse
#################
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ $ cabal --key cabal://0b2a6c1c58014fe0da6dff38df6282157c405bc0ed7b550cda5c8c43d8
## Usage
#### Start a new instance:
```
cabal --db <file path>
cabal --new
```

#### Connect to an existing instance:
Expand Down Expand Up @@ -90,3 +90,5 @@ cabal --key <key> --seeder
&nbsp;&nbsp;&nbsp;&nbsp;scroll down through backlog
`alt-[1,9]`
&nbsp;&nbsp;&nbsp;&nbsp;select channels 1-9
`alt-n`
&nbsp;&nbsp;&nbsp;&nbsp;tab between the cabals & channels panes
243 changes: 210 additions & 33 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,44 @@
var Cabal = require('cabal-core')
var swarm = require('cabal-core/swarm.js')
var minimist = require('minimist')
var os = require('os')
var fs = require('fs')
var yaml = require('js-yaml')
var mkdirp = require('mkdirp')
var frontend = require('./neat-screen.js')
var crypto = require('hypercore-crypto')
var chalk = require('chalk')

var args = minimist(process.argv.slice(2))

var homedir = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE
var rootdir = args.dir || (homedir + '/.cabal/archives/')
var homedir = os.homedir()
var rootdir = args.dir || (homedir + `/.cabal/v${Cabal.databaseVersion}`)
var rootconfig = `${rootdir}/config.yml`
var archivesdir = `${rootdir}/archives/`

var usage = `Usage
cabal --key cabal://key
cabal cabal://key
cabal <your saved --alias of a cabal>
OR
cabal --db /path/to/db
cabal --new
Options:
--seed Start a headless seed for the specified cabal key
--new Start a new cabal
--nick Your nickname
--alias Save an alias for the specified cabal, use with --key
--aliases Print out your saved cabal aliases
--forget Forgets the specified alias
--clear Clears out all aliases
--key Specify a cabal key. Used with --alias
--join Only join the specified cabal, disregarding whatever is in the config
--config Specify a full path to a cabal config
--message Publish a single message; then quit after \`timeout\`
--channel Channel name to publish to for \`message\` option; default: "default"
--timeout Delay in milliseconds to wait on swarm before quitting for \`message\` option; default: 5000
Expand All @@ -29,57 +48,215 @@ var usage = `Usage
Work in progress! Learn more at github.com/cabal-club
`

var config
var cabalKeys = []
var configFilePath = findConfigPath()

// make sure the .cabal/v<databaseVersion> folder exists
mkdirp.sync(rootdir)

// create a default config in rootdir if it doesn't exist
if (!fs.existsSync(rootconfig)) {
saveConfig(rootconfig, { cabals: [], aliases: {} })
}

// Attempt to load local or homedir config file
try {
if (configFilePath) {
config = yaml.safeLoad(fs.readFileSync(configFilePath, 'utf8'))
if (!config.cabals) { config.cabals = [] }
if (!config.aliases) { config.aliases = {} }
cabalKeys = config.cabals
}
} catch (e) {
logError(e)
process.exit(1)
}

if (args.clear) {
delete config['aliases']
saveConfig(configFilePath, config)
process.stdout.write('Aliases cleared\n')
process.exit(0)
}

if (args.forget) {
delete config.aliases[args.forget]
saveConfig(configFilePath, config)
process.stdout.write(`${args.forget} has been forgotten`)
process.exit(0)
}

if (args.aliases) {
var aliases = Object.keys(config.aliases)
if (aliases.length === 0) {
process.stdout.write("You don't have any saved aliases.\n\n")
process.stdout.write(`Save an alias by running\n`)
process.stdout.write(`${chalk.magentaBright('cabal: ')} ${chalk.greenBright('--alias cabal://c001..c4b41')} `)
process.stdout.write(`${chalk.blueBright('--key your-alias-name')}\n`)
} else {
aliases.forEach(function (alias) {
process.stdout.write(`${chalk.blueBright(alias)}\t\t ${chalk.greenBright(config.aliases[alias])}\n`)
})
}
process.exit(0)
}

if (args.alias && !args.key) {
logError('the --alias option needs to be used together with --key')
process.exit(1)
}

// user wants to alias a cabal:// key with a name
if (args.alias && args.key) {
config.aliases[args.alias] = args.key
saveConfig(configFilePath, config)
console.log(`${chalk.magentaBright('cabal:')} saved ${chalk.greenBright(args.key)} as ${chalk.blueBright(args.alias)}`)
process.exit(0)
}

if (args.key) {
args.key = args.key.replace('cabal://', '').replace('cbl://', '').replace('dat://', '').replace(/\//g, '')
args.db = rootdir + args.key
// If a key is provided, place it at the top of the list
cabalKeys.unshift(args.key)
} else if (args._.length > 0) {
// the cli was run as `cabal <alias|key> ... <alias|key>`
args._.forEach(function (str) {
cabalKeys.unshift(getKey(str))
})
}

// disregard config
if (args.join) {
cabalKeys = [getKey(args.join)]
}

// only enable multi-cabal under the --experimental flag
if (!args.experimental && cabalKeys.length) {
var firstKey = cabalKeys[0]
cabalKeys = [firstKey]
}

var cabal = Cabal(args.db, args.key)
// create and join a new cabal
if (args.new) {
var key = crypto.keyPair().publicKey.toString('hex')
var db = archivesdir + key
var cabal = Cabal(db, key)
cabal.db.ready(function () {
start(args.key)
if (!args.seed) {
start([cabal])
}
})
} else {
cabal = Cabal(args.db, null)
cabal.db.ready(function () {
cabal.getLocalKey(function (err, key) {
if (err) throw err
start(key)
} else if (cabalKeys.length) {
// join the specified list of cabals
Promise.all(cabalKeys.map((key) => {
key = key.replace('cabal://', '').replace('cbl://', '').replace('dat://', '').replace(/\//g, '')
var db = archivesdir + key
var cabal = Cabal(db, key)
return new Promise((resolve) => {
cabal.db.ready(() => {
resolve(cabal)
})
})
})).then((cabals) => {
start(cabals)
})
}

if (!args.db) {
} else {
process.stderr.write(usage)
process.exit(1)
}

function start (key) {
function start (cabals) {
if (!args.seed) {
if (args.message) {
if (args.key && args.message) {
publishSingleMessage({
key: args.key,
channel: args.channel,
message: args.message,
messageType: args.type,
timeout: args.timeout
})
return
}
frontend(cabal)
setTimeout(function () { swarm(cabal) }, 300)

// => remembers the latest cabal, allows joining latest with `cabal`
// TODO: rewrite this when the multi-cabal functionality comes out from
// behind its experimental flag
if (!args.join && !args.experimental) {
// unsure about this, it effectively removes all of the cabals in the config
// but then again we don't have a method to save them either right now so
// let's run with it and fix after the bugs
config.cabals = cabals.map((c) => c.key)
saveConfig(configFilePath, config)
}

var dbVersion = Cabal.databaseVersion
var isExperimental = (typeof args.experimental !== 'undefined')
frontend({
isExperimental,
archivesdir,
cabals,
configFilePath,
homedir,
dbVersion,
config,
rootdir
})
setTimeout(() => {
cabals.forEach((cabal) => {
swarm(cabal)
})
}, 300)
} else {
console.log('Seeding', key)
swarm(cabal)
cabals.forEach((cabal) => {
console.log('Seeding', cabal.key)
swarm(cabal)
})
}
}

function publishSingleMessage ({channel, message, messageType, timeout}) {
console.log('Publishing message to channel - ' + channel + ': "' + message + '"...')
cabal.publish({
type: messageType || 'chat/text',
content: {
channel: channel || 'default',
text: message
}
function getKey (str) {
// return key if what was passed in was a saved alias
if (str in config.aliases) { return config.aliases[str] }
// else assume it's a cabal key
return str
}

function logError (msg) {
console.error(`${chalk.red('cabal:')} ${msg}`)
}

function findConfigPath () {
var currentDirConfigFilename = '.cabal.yml'
if (args.config && fs.existsSync(args.config)) {
return args.config
} else if (fs.existsSync(currentDirConfigFilename)) {
return currentDirConfigFilename
}
return rootconfig
}

function saveConfig (path, config) {
// make sure config is well-formatted (contains all config options)
if (!config.cabals) { config.cabals = [] }
if (!config.aliases) { config.aliases = {} }
let data = yaml.safeDump(config, {
sortKeys: true
})
fs.writeFileSync(path, data, 'utf8')
}

function publishSingleMessage ({key, channel, message, messageType, timeout}) {
console.log(`Publishing message to channel - ${channel || 'default'}: ${message}`)
var cabal = Cabal(archivesdir + key, key)
cabal.db.ready(() => {
cabal.publish({
type: messageType || 'chat/text',
content: {
channel: channel || 'default',
text: message
}
})
swarm(cabal)
setTimeout(function () { process.exit(0) }, timeout || 5000)
})
swarm(cabal)
setTimeout(function () { process.exit(0) }, timeout || 5000)
}
28 changes: 27 additions & 1 deletion commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ function Commander (view, cabal) {
self.view.writeLine.bind(self.view)(`/${key}`)
self.view.writeLine.bind(self.view)(` ${self.commands[key].help()}`)
}
self.view.writeLine.bind(self.view)(`alt-n`)
self.view.writeLine.bind(self.view)(` move between channels/cabals panes`)
self.view.writeLine.bind(self.view)(`ctrl+{n,p}`)
self.view.writeLine.bind(self.view)(` move up/down channels/cabals`)
}
},
debug: {
Expand All @@ -91,6 +95,12 @@ function Commander (view, cabal) {
process.exit(0)
}
},
exit: {
help: () => 'exit the cabal process',
call: (arg) => {
process.exit(0)
}
},
topic: {
help: () => 'set the topic/description/`message of the day` for a channel',
call: (arg) => {
Expand All @@ -99,10 +109,26 @@ function Commander (view, cabal) {
}
}
// add aliases to commands
this.alias('emote', 'me')
this.alias('join', 'j')
this.alias('nick', 'n')
this.alias('emote', 'me')
this.alias('topic', 'motd')

// add in experimental commands
if (self.view.isExperimental) {
self.commands['add'] = {
help: () => 'add a cabal',
call: (arg) => {
if (arg === '') {
self.view.writeLine('* Usage example: /add cabalkey')
return
}
self.channel = arg
self.view.addCabal(arg)
}
}
self.alias('add', 'cabal')
}
}

Commander.prototype.alias = function (command, alias) {
Expand Down
Loading

0 comments on commit cf322c0

Please sign in to comment.