├── .gitignore ├── bin └── replem ├── test.sh ├── lib ├── parse-argv.js ├── npm.js ├── help.js ├── format-installed-list.js └── index.js ├── .eslintrc ├── Makefile ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /bin/replem: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib'); 4 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ex 3 | 4 | rm -rf ~/.replem/node_modules 5 | ./bin/replem --no-repl ramda:R sanctuary! 6 | ./bin/replem --no-repl ramda@0.17 7 | ./bin/replem --no-repl ramda/ramda 8 | -------------------------------------------------------------------------------- /lib/parse-argv.js: -------------------------------------------------------------------------------- 1 | const minimist = require('minimist'); 2 | 3 | module.exports = (argv) => 4 | minimist(argv.slice(2), { 5 | alias: { h: 'help', v: 'verbose' }, 6 | boolean: ['help', 'verbose'] 7 | }); 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | rules: 2 | indent: 3 | - 2 4 | - 2 5 | quotes: 6 | - 2 7 | - single 8 | linebreak-style: 9 | - 2 10 | - unix 11 | semi: 12 | - 2 13 | - always 14 | no-console: 15 | - 0 16 | globals: 17 | dedent: false 18 | env: 19 | es6: true 20 | node: true 21 | mocha: true 22 | extends: 'eslint:recommended' 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | 3 | NAME = $(shell node -e "console.log(require('./package.json').name)") 4 | 5 | install: all 6 | npm install -g . 7 | 8 | reinstall: clean 9 | $(MAKE) uninstall 10 | $(MAKE) install 11 | 12 | uninstall: 13 | npm uninstall -g ${NAME} 14 | 15 | dev-install: package.json 16 | npm install . 17 | 18 | publish: all test 19 | git push --tags origin HEAD:master 20 | npm publish 21 | -------------------------------------------------------------------------------- /lib/npm.js: -------------------------------------------------------------------------------- 1 | const { Future } = require('ramda-fantasy'); 2 | const npm = require('npm'); 3 | const noop = () => {}; 4 | const log = console.log; 5 | 6 | exports.load = (opts) => 7 | Future((rej, res) => 8 | npm.load(opts, (err, data) => 9 | err ? rej(err) : res(data) 10 | )); 11 | 12 | exports.install = (packages) => 13 | Future((rej, res) => { 14 | console.log = noop; 15 | npm.commands.install(packages, (err, out) => { 16 | console.log = log; 17 | err ? rej(err) : res(out); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /lib/help.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | Usage: replem [options] [[:]]... 3 | 4 | --repl require a custom repl 5 | -v, --verbose enable verbose output 6 | -h, --help displays help 7 | 8 | Examples: 9 | 10 | replem ramda:R # Install and provide ramda as variable R 11 | replem ramda! # Extends REPL context with all functions of ramda 12 | replem ecto/node-timeago # Installs a module from GitHub 13 | replem lodash@3.0.0 # Installs a module at specific version 14 | 15 | Version: ${require('../package.json').version} 16 | 17 | README: https://github.com/raine/replem` 18 | .replace(/^\n/, ''); 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "replem", 3 | "version": "2.0.1", 4 | "description": "Instantly try npm modules in REPL environment", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "lint": "eslint lib" 8 | }, 9 | "author": "Raine Virta ", 10 | "license": "ISC", 11 | "repository": "raine/replem", 12 | "bin": { 13 | "replem": "./bin/replem" 14 | }, 15 | "keywords": [ 16 | "repl" 17 | ], 18 | "dependencies": { 19 | "camelcase": "^1.2.1", 20 | "chalk": "^1.1.1", 21 | "char-spinner": "^1.0.1", 22 | "debug": "^2.2.0", 23 | "glob": "^5.0.14", 24 | "minimist": "^1.2.0", 25 | "npm": "4.3.0", 26 | "npm-package-arg": "^4.0.2", 27 | "ramda": "0.18.x", 28 | "ramda-fantasy": "^0.4.0", 29 | "repl.history": "^0.1.3", 30 | "sanctuary": "0.7.x", 31 | "xtend": "^4.0.0" 32 | }, 33 | "devDependencies": { 34 | "eslint": "^1.4.3" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/format-installed-list.js: -------------------------------------------------------------------------------- 1 | const { concat, curry, filter, join, last, map, pipe, split, take } = require('ramda'); 2 | const { green, cyan } = require('chalk'); 3 | const S = require('sanctuary'); 4 | 5 | // startsWith :: String -> String -> Boolean 6 | const startsWith = curry((x, str) => str.indexOf(x) === 0); 7 | 8 | const unwords = join(' '); 9 | const getResolvedSha = pipe( split('#'), last, take(7) ); 10 | 11 | const formatVersion = (resolved, version) => 12 | pipe(S.toMaybe, 13 | filter(startsWith('git://')), 14 | map(getResolvedSha), 15 | S.maybe(`@${version}`, concat('#')) 16 | )(resolved); 17 | 18 | const formatInstalledList = 19 | pipe(map(pipe( 20 | ({alias, name, version, _resolved}) => 21 | unwords([ 22 | cyan(`${name}${formatVersion(_resolved, version)}`), 23 | 'as', 24 | green(alias) 25 | ]), 26 | concat(' - '))), 27 | join('\n')); 28 | 29 | 30 | module.exports = formatInstalledList; 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # repl'em [![npm version](https://badge.fury.io/js/replem.svg)](https://www.npmjs.com/package/replem) 2 | 3 | Instantly try npm modules in REPL environment. 4 | 5 | 6 | 7 | ## features 8 | 9 | - Install modules from npm, optionally at specific version. 10 | - Install modules directly from GitHub at particular commit or branch. 11 | - Use a custom REPL like `coffee-script/repl`. 12 | - Retains history of past sessions. 13 | 14 | ## install 15 | 16 | ```sh 17 | $ npm install -g replem 18 | ``` 19 | 20 | ## usage 21 | 22 | ``` 23 | replem [options] [[:]]... 24 | 25 | --repl require a custom repl 26 | -v, --verbose enable verbose output 27 | -h, --help displays help 28 | ``` 29 | 30 | Launches a REPL session with specified packages installed and available in 31 | the context. 32 | 33 | ## arguments 34 | 35 | Uses [`npm install`](https://docs.npmjs.com/cli/install) internally, so 36 | similar types of arguments are accepted. 37 | 38 | For example: 39 | 40 | - Install a specific version: `replem lodash@3.0.0` 41 | - Install a module from GitHub: `replem githubname/reponame#commit` 42 | 43 | By postfixing module's name with `:` you can set an alias for a 44 | module. Module's exports will be available under this name. 45 | 46 | ```sh 47 | $ replem ramda:R 48 | Installed into REPL context: 49 | - ramda@0.17.1 as R 50 | > R.inc(1) // 2 51 | ``` 52 | 53 | With a bang (`!`) after everything, all module's properties will be directly 54 | available in context: 55 | 56 | ```sh 57 | $ replem ramda! 58 | Installed into REPL context: 59 | - ramda@0.17.1 as ramda 60 | > reduce === ramda.reduce 61 | true 62 | ``` 63 | 64 | ## custom repl 65 | 66 | To use a custom repl, install it to `~/.replem/node_modules` first: 67 | 68 | ```sh 69 | $ npm install --prefix ~/.replem coffee-script 70 | $ replem --repl coffee-script/repl lodash 71 | > (n * 2 for n in [0..5]) 72 | Array [ 0, 2, 4, 6, 8, 10 ] 73 | ``` 74 | 75 | ## requiring from inside installed modules 76 | 77 | The REPL context is provided with the function `replem.require()` that can be 78 | used to require from under `~/.replem/node_modules`. 79 | 80 | ## caveats 81 | 82 | - Multiple versions of the same module cannot be used concurrently. 83 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const joinPath = require('path').join; 2 | 3 | const extend = require('xtend/mutable'); 4 | const camelCase = require('camelcase'); 5 | const spinner = require('char-spinner'); 6 | const replHistory = require('repl.history'); 7 | const npa = require('npm-package-arg'); 8 | const S = require('sanctuary'); 9 | const _glob = require('glob'); 10 | const fs = require('fs'); 11 | const { Future } = require('ramda-fantasy'); 12 | const { __, chain, commute, complement, concat, cond, curry, curryN, evolve, find, head, ifElse, intersection, isEmpty, join, map, merge, mergeAll, nth, objOf, partial, path, pipe, project, propEq, replace, T, tail, tap, toUpper, unary } = require('ramda'); 13 | const help = require('./help'); 14 | const npm = require('./npm'); 15 | const formatInstalledList = require('./format-installed-list'); 16 | const parseArgv = require('./parse-argv'); 17 | 18 | // overlaps :: [a] -> [a] -> Boolean 19 | const overlaps = pipe(intersection, complement(isEmpty)); 20 | const join2 = curryN(2, joinPath); 21 | const unlines = join('\n'); 22 | const die = (err) => { 23 | console.error(err.message || err); 24 | process.exit(1); 25 | }; 26 | 27 | if (overlaps(['-v', '--verbose'], process.argv)) 28 | require('debug').enable('replem'); 29 | 30 | const debug = require('debug')('replem'); 31 | 32 | const capitalize = (str) => concat(toUpper(head(str)), tail(str)); 33 | const pascalCase = pipe(camelCase, capitalize); 34 | const isUpper = (c) => toUpper(c) === c; 35 | const isCapitalized = pipe(head, isUpper); 36 | const pkgNameAsVar = ifElse(isCapitalized, pascalCase, camelCase); 37 | 38 | const ALIAS = /:([^!]+)/; 39 | const EXTEND = /!$/; 40 | const parseAlias = pipe( 41 | S.match(ALIAS), chain(nth(1))); 42 | const parseExtend = pipe( 43 | S.match(EXTEND), map(T)); 44 | const rm = replace(__, ''); 45 | const cleanArg = pipe(...map(rm, [ EXTEND, ALIAS ])); 46 | const orEmpty = S.fromMaybe({}); 47 | 48 | const parsePositionalArg = (arg) => { 49 | const cleaned = cleanArg(arg); 50 | return mergeAll([ 51 | orEmpty(map(objOf('alias'), parseAlias(arg))), 52 | orEmpty(map(objOf('extend'), parseExtend(arg))), 53 | { npa: npa(cleaned) } 54 | ]); 55 | }; 56 | 57 | const contextForPkg = curry((_require, obj) => { 58 | const module = _require(obj.name); 59 | return merge( 60 | obj.extend ? module : {}, 61 | { [obj.alias]: module } 62 | ); 63 | }); 64 | 65 | const makeReplContext = (_require, pkgData) => 66 | mergeAll(concat(map(contextForPkg(_require), pkgData), [ 67 | { replem: { require: _require, 68 | modules: pkgData } } 69 | ])); 70 | 71 | // glob :: String -> Future Error [String] 72 | const glob = (path) => Future((rej, res) => 73 | _glob(path, (e, files) => e ? rej(e) : res(files))); 74 | 75 | // readFile :: String -> String -> Future Error String 76 | const readFile = curry((encoding, filename) => 77 | Future((rej, res) => 78 | fs.readFile(filename, encoding, (e, data) => 79 | e ? rej(e) : res(data)))); 80 | 81 | // traverse :: Applicative f => (a -> f b) -> t a -> f (t b) 82 | const traverse = (fn) => pipe(map(fn), commute(Future.of)); 83 | 84 | // readDeps :: String -> Future Error [Object] 85 | const readDeps = pipe( 86 | (p) => joinPath(p, '*', 'package.json'), 87 | glob, 88 | chain(traverse(readFile('utf8'))), 89 | map(map(unary(JSON.parse))) 90 | ); 91 | 92 | const makePkgMatchPred = cond([ 93 | [ propEq('type', 'range'), 94 | (npa) => (pkg) => 95 | pkg._from === npa.raw || 96 | pkg._from === npa.name || 97 | pkg._from === `${npa.name}@${npa.spec}` ], 98 | [ T, 99 | (npa) => (pkg) => 100 | pkg._from === npa.raw || 101 | pkg._from === npa.name ] 102 | ]); 103 | 104 | // mergePkgData :: String -> [Object] -> Future Error [Object] 105 | const mergePkgData = (modulesPath, pkgObjs) => 106 | readDeps(modulesPath) 107 | .map(project(['_from', '_resolved', 'name', 'version'])) 108 | // _from in package.json is "ramda@latest" when install string is "ramda" 109 | .map(map(evolve({ _from: replace(/@.*$/, '') }))) 110 | .map(data => 111 | map(arg => 112 | merge(arg, find(makePkgMatchPred(arg.npa), data)) 113 | , pkgObjs)); 114 | 115 | const defaultAliasToName = (pkg) => 116 | merge({ alias: pkgNameAsVar(pkg.name) }, pkg); 117 | 118 | const main = (process) => { 119 | const replemPath = join2(process.env.HOME, '.replem'); 120 | const replemModules = join2(replemPath, 'node_modules'); 121 | const replemRequire = pipe(join2(replemModules), require); 122 | const argv = parseArgv(process.argv); 123 | const pkgObjs = map(parsePositionalArg, argv._); 124 | const rawPkgNames = map(path(['npa', 'raw']), pkgObjs); 125 | debug('parsed args', pkgObjs); 126 | 127 | if (argv.help || isEmpty(rawPkgNames)) die(help); 128 | const interval = spinner(); 129 | 130 | npm.load({ 131 | progress: false, 132 | loglevel: argv.verbose ? 'verbose' : 'silent', 133 | prefix: replemPath 134 | }) 135 | .chain(() => npm.install(rawPkgNames)) 136 | .chain(() => { 137 | clearInterval(interval); 138 | return mergePkgData(replemModules, pkgObjs); 139 | }) 140 | .map(tap(partial(debug, ['pkg data']))) 141 | .map(map(defaultAliasToName)) 142 | .fork(die, (pkgData) => { 143 | console.log(unlines([ 144 | 'Installed into REPL context:', 145 | formatInstalledList(pkgData) 146 | ])); 147 | 148 | const repl = argv.repl ? replemRequire(argv.repl) : require('repl'); 149 | const r = repl.start({ 150 | prompt: '> ', 151 | useGlobal: true 152 | }); 153 | extend(r.context, makeReplContext(replemRequire, pkgData)); 154 | replHistory(r, join2(replemPath, 'history')); 155 | if (argv.repl === false) r.close(); 156 | }); 157 | }; 158 | 159 | main(process); 160 | --------------------------------------------------------------------------------