├── .babelrc ├── .electron-vue ├── build.js ├── dev-client.js ├── dev-runner.js ├── webpack.main.config.js ├── webpack.renderer.config.js └── webpack.web.config.js ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── build └── icons │ ├── 256x256.png │ ├── icon.icns │ └── icon.ico ├── icons ├── icon.icns └── icon.ico ├── package-lock.json ├── package.json ├── src ├── index.ejs ├── main │ ├── executable │ │ ├── executable.js │ │ ├── index.js │ │ ├── jupyter.js │ │ ├── pipenv.js │ │ ├── pyenv.js │ │ └── python.js │ ├── index.dev.js │ ├── index.js │ ├── utils │ │ ├── case-converter.js │ │ └── refresh-path.js │ └── worker.js └── renderer │ ├── App.vue │ ├── assets │ ├── .gitkeep │ ├── font-awesome │ │ ├── HELP-US-OUT.txt │ │ ├── css │ │ │ ├── font-awesome.css │ │ │ └── font-awesome.min.css │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ ├── less │ │ │ ├── animated.less │ │ │ ├── bordered-pulled.less │ │ │ ├── core.less │ │ │ ├── fixed-width.less │ │ │ ├── font-awesome.less │ │ │ ├── icons.less │ │ │ ├── larger.less │ │ │ ├── list.less │ │ │ ├── mixins.less │ │ │ ├── path.less │ │ │ ├── rotated-flipped.less │ │ │ ├── screen-reader.less │ │ │ ├── stacked.less │ │ │ └── variables.less │ │ └── scss │ │ │ ├── _animated.scss │ │ │ ├── _bordered-pulled.scss │ │ │ ├── _core.scss │ │ │ ├── _fixed-width.scss │ │ │ ├── _icons.scss │ │ │ ├── _larger.scss │ │ │ ├── _list.scss │ │ │ ├── _mixins.scss │ │ │ ├── _path.scss │ │ │ ├── _rotated-flipped.scss │ │ │ ├── _screen-reader.scss │ │ │ ├── _stacked.scss │ │ │ ├── _variables.scss │ │ │ └── font-awesome.scss │ ├── fonts.css │ ├── fonts │ │ ├── inconsolata-webfont.woff │ │ └── inconsolata-webfont.woff2 │ ├── icons │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 24x24.png │ │ ├── 256x256.png │ │ ├── 32x32.png │ │ ├── 48x48.png │ │ ├── 512x512.png │ │ ├── 64x64.png │ │ └── 96x96.png │ └── logo.png │ ├── components │ ├── AboutView.vue │ ├── DisplayDate.vue │ ├── DisplayPath.vue │ ├── ErrorView.vue │ ├── FileLink.vue │ ├── PythonDetailView.vue │ ├── PythonInstallerView.vue │ ├── PythonListView.vue │ ├── PythonPackagesView.vue │ ├── PythonRow.vue │ ├── PythonTable.vue │ ├── Sidebar.vue │ ├── WalkthroughView.vue │ ├── installers │ │ ├── activepython │ │ │ ├── Description.vue │ │ │ ├── Install.vue │ │ │ └── Uninstall.vue │ │ ├── anaconda │ │ │ ├── Description.vue │ │ │ ├── Install.vue │ │ │ └── Uninstall.vue │ │ ├── canopy │ │ │ ├── Description.vue │ │ │ ├── Install.vue │ │ │ └── Uninstall.vue │ │ ├── homebrew │ │ │ ├── Description.vue │ │ │ ├── Install.vue │ │ │ └── Uninstall.vue │ │ ├── index.js │ │ ├── miniconda │ │ │ ├── Description.vue │ │ │ ├── Install.vue │ │ │ └── Uninstall.vue │ │ ├── pipenv │ │ │ ├── Description.vue │ │ │ ├── Install.vue │ │ │ └── Uninstall.vue │ │ ├── pyenv │ │ │ ├── Description.vue │ │ │ ├── Install.vue │ │ │ └── Uninstall.vue │ │ ├── pythonorg │ │ │ ├── Description.vue │ │ │ ├── Install.vue │ │ │ └── Uninstall.vue │ │ ├── winpython │ │ │ ├── Description.vue │ │ │ ├── Install.vue │ │ │ └── Uninstall.vue │ │ └── xy │ │ │ ├── Description.vue │ │ │ ├── Install.vue │ │ │ └── Uninstall.vue │ └── walkthrough │ │ ├── JupyterStep.vue │ │ ├── PipenvStep.vue │ │ ├── PyenvStep.vue │ │ ├── PythonRemoval.vue │ │ ├── PythonThreeStep.vue │ │ └── Step.vue │ ├── main.js │ ├── router │ └── index.js │ └── store │ └── index.js ├── static ├── .gitkeep ├── font-awesome │ ├── HELP-US-OUT.txt │ ├── css │ │ ├── font-awesome.css │ │ └── font-awesome.min.css │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── less │ │ ├── animated.less │ │ ├── bordered-pulled.less │ │ ├── core.less │ │ ├── fixed-width.less │ │ ├── font-awesome.less │ │ ├── icons.less │ │ ├── larger.less │ │ ├── list.less │ │ ├── mixins.less │ │ ├── path.less │ │ ├── rotated-flipped.less │ │ ├── screen-reader.less │ │ ├── stacked.less │ │ └── variables.less │ └── scss │ │ ├── _animated.scss │ │ ├── _bordered-pulled.scss │ │ ├── _core.scss │ │ ├── _fixed-width.scss │ │ ├── _icons.scss │ │ ├── _larger.scss │ │ ├── _list.scss │ │ ├── _mixins.scss │ │ ├── _path.scss │ │ ├── _rotated-flipped.scss │ │ ├── _screen-reader.scss │ │ ├── _stacked.scss │ │ ├── _variables.scss │ │ └── font-awesome.scss └── icons │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ └── 96x96.png ├── test ├── .eslintrc ├── e2e │ ├── index.js │ ├── specs │ │ └── Launch.spec.js │ └── utils.js └── unit │ ├── index.js │ ├── karma.conf.js │ └── specs │ └── LandingPage.spec.js ├── yarn-error.log └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "comments": false, 3 | "env": { 4 | "main": { 5 | "presets": [ 6 | ["env", { 7 | "targets": { "node": 7 } 8 | }], 9 | "stage-0" 10 | ] 11 | }, 12 | "renderer": { 13 | "presets": [ 14 | ["env", { 15 | "modules": false 16 | }], 17 | "stage-0" 18 | ] 19 | }, 20 | "web": { 21 | "presets": [ 22 | ["env", { 23 | "modules": false 24 | }], 25 | "stage-0" 26 | ] 27 | } 28 | }, 29 | "plugins": ["transform-runtime"] 30 | } 31 | -------------------------------------------------------------------------------- /.electron-vue/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | const { say } = require('cfonts') 6 | const chalk = require('chalk') 7 | const del = require('del') 8 | const { spawn } = require('child_process') 9 | const webpack = require('webpack') 10 | const Multispinner = require('multispinner') 11 | 12 | 13 | const mainConfig = require('./webpack.main.config') 14 | const rendererConfig = require('./webpack.renderer.config') 15 | // const workerConfig = require('./webpack.worker.config') 16 | const webConfig = require('./webpack.web.config') 17 | 18 | const doneLog = chalk.bgGreen.white(' DONE ') + ' ' 19 | const errorLog = chalk.bgRed.white(' ERROR ') + ' ' 20 | const okayLog = chalk.bgBlue.white(' OKAY ') + ' ' 21 | const isCI = process.env.CI || false 22 | 23 | if (process.env.BUILD_TARGET === 'clean') clean() 24 | else if (process.env.BUILD_TARGET === 'web') web() 25 | else build() 26 | 27 | function clean () { 28 | del.sync(['build/*', '!build/icons', '!build/icons/icon.*']) 29 | console.log(`\n${doneLog}\n`) 30 | process.exit() 31 | } 32 | 33 | function build () { 34 | greeting() 35 | 36 | del.sync(['dist/electron/*', '!.gitkeep']) 37 | 38 | // const tasks = ['main', 'worker', 'renderer'] 39 | const tasks = ['main', 'renderer'] 40 | const m = new Multispinner(tasks, { 41 | preText: 'building', 42 | postText: 'process' 43 | }) 44 | 45 | let results = '' 46 | 47 | m.on('success', () => { 48 | process.stdout.write('\x1B[2J\x1B[0f') 49 | console.log(`\n\n${results}`) 50 | console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`) 51 | process.exit() 52 | }) 53 | 54 | pack(mainConfig).then(result => { 55 | results += result + '\n\n' 56 | m.success('main') 57 | }).catch(err => { 58 | m.error('main') 59 | console.log(`\n ${errorLog}failed to build main process`) 60 | console.error(`\n${err}\n`) 61 | process.exit(1) 62 | }) 63 | 64 | pack(rendererConfig).then(result => { 65 | results += result + '\n\n' 66 | m.success('renderer') 67 | }).catch(err => { 68 | m.error('renderer') 69 | console.log(`\n ${errorLog}failed to build renderer process`) 70 | console.error(`\n${err}\n`) 71 | process.exit(1) 72 | }) 73 | 74 | // pack(workerConfig).then(result => { 75 | // results += result + '\n\n' 76 | // m.success('worker') 77 | // }).catch(err => { 78 | // m.error('worker') 79 | // console.log(`\n ${errorLog}failed to build worker process`) 80 | // console.error(`\n${err}\n`) 81 | // process.exit(1) 82 | // }) 83 | 84 | } 85 | 86 | function pack (config) { 87 | return new Promise((resolve, reject) => { 88 | webpack(config, (err, stats) => { 89 | if (err) reject(err.stack || err) 90 | else if (stats.hasErrors()) { 91 | let err = '' 92 | 93 | stats.toString({ 94 | chunks: false, 95 | colors: true 96 | }) 97 | .split(/\r?\n/) 98 | .forEach(line => { 99 | err += ` ${line}\n` 100 | }) 101 | 102 | reject(err) 103 | } else { 104 | resolve(stats.toString({ 105 | chunks: false, 106 | colors: true 107 | })) 108 | } 109 | }) 110 | }) 111 | } 112 | 113 | function web () { 114 | del.sync(['dist/web/*', '!.gitkeep']) 115 | webpack(webConfig, (err, stats) => { 116 | if (err || stats.hasErrors()) console.log(err) 117 | 118 | console.log(stats.toString({ 119 | chunks: false, 120 | colors: true 121 | })) 122 | 123 | process.exit() 124 | }) 125 | } 126 | 127 | function greeting () { 128 | const cols = process.stdout.columns 129 | let text = '' 130 | 131 | if (cols > 85) text = 'lets-build' 132 | else if (cols > 60) text = 'lets-|build' 133 | else text = false 134 | 135 | if (text && !isCI) { 136 | say(text, { 137 | colors: ['yellow'], 138 | font: 'simple3d', 139 | space: false 140 | }) 141 | } else console.log(chalk.yellow.bold('\n lets-build')) 142 | console.log() 143 | } 144 | -------------------------------------------------------------------------------- /.electron-vue/dev-client.js: -------------------------------------------------------------------------------- 1 | const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 2 | 3 | hotClient.subscribe(event => { 4 | /** 5 | * Reload browser when HTMLWebpackPlugin emits a new index.html 6 | * 7 | * Currently disabled until jantimon/html-webpack-plugin#680 is resolved. 8 | * https://github.com/SimulatedGREG/electron-vue/issues/437 9 | * https://github.com/jantimon/html-webpack-plugin/issues/680 10 | */ 11 | // if (event.action === 'reload') { 12 | // window.location.reload() 13 | // } 14 | 15 | /** 16 | * Notify `mainWindow` when `main` process is compiling, 17 | * giving notice for an expected reload of the `electron` process 18 | */ 19 | if (event.action === 'compiling') { 20 | document.body.innerHTML += ` 21 | 34 | 35 |
36 | Compiling Main Process... 37 |
38 | ` 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /.electron-vue/dev-runner.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const chalk = require('chalk') 4 | const electron = require('electron') 5 | const path = require('path') 6 | const { say } = require('cfonts') 7 | const { spawn } = require('child_process') 8 | const webpack = require('webpack') 9 | const WebpackDevServer = require('webpack-dev-server') 10 | const webpackHotMiddleware = require('webpack-hot-middleware') 11 | 12 | const mainConfig = require('./webpack.main.config') 13 | const rendererConfig = require('./webpack.renderer.config') 14 | 15 | let electronProcess = null 16 | let manualRestart = false 17 | let hotMiddleware 18 | 19 | function logStats (proc, data) { 20 | let log = '' 21 | 22 | log += chalk.yellow.bold(`┏ ${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`) 23 | log += '\n\n' 24 | 25 | if (typeof data === 'object') { 26 | data.toString({ 27 | colors: true, 28 | chunks: false 29 | }).split(/\r?\n/).forEach(line => { 30 | log += ' ' + line + '\n' 31 | }) 32 | } else { 33 | log += ` ${data}\n` 34 | } 35 | 36 | log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n' 37 | 38 | console.log(log) 39 | } 40 | 41 | function startRenderer () { 42 | return new Promise((resolve, reject) => { 43 | rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer) 44 | 45 | const compiler = webpack(rendererConfig) 46 | hotMiddleware = webpackHotMiddleware(compiler, { 47 | log: false, 48 | heartbeat: 2500 49 | }) 50 | 51 | compiler.plugin('compilation', compilation => { 52 | compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => { 53 | hotMiddleware.publish({ action: 'reload' }) 54 | cb() 55 | }) 56 | }) 57 | 58 | compiler.plugin('done', stats => { 59 | logStats('Renderer', stats) 60 | }) 61 | 62 | const server = new WebpackDevServer( 63 | compiler, 64 | { 65 | contentBase: path.join(__dirname, '../'), 66 | quiet: true, 67 | before (app, ctx) { 68 | app.use(hotMiddleware) 69 | ctx.middleware.waitUntilValid(() => { 70 | resolve() 71 | }) 72 | } 73 | } 74 | ) 75 | 76 | server.listen(9080) 77 | }) 78 | } 79 | 80 | function startMain () { 81 | return new Promise((resolve, reject) => { 82 | mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main) 83 | 84 | const compiler = webpack(mainConfig) 85 | 86 | compiler.plugin('watch-run', (compilation, done) => { 87 | logStats('Main', chalk.white.bold('compiling...')) 88 | hotMiddleware.publish({ action: 'compiling' }) 89 | done() 90 | }) 91 | 92 | compiler.watch({}, (err, stats) => { 93 | if (err) { 94 | console.log(err) 95 | return 96 | } 97 | 98 | logStats('Main', stats) 99 | 100 | if (electronProcess && electronProcess.kill) { 101 | manualRestart = true 102 | process.kill(electronProcess.pid) 103 | electronProcess = null 104 | startElectron() 105 | 106 | setTimeout(() => { 107 | manualRestart = false 108 | }, 5000) 109 | } 110 | 111 | resolve() 112 | }) 113 | }) 114 | } 115 | 116 | function startElectron () { 117 | electronProcess = spawn(electron, ['--inspect=5858', path.join(__dirname, '../dist/electron/main.js')]) 118 | 119 | electronProcess.stdout.on('data', data => { 120 | electronLog(data, 'blue') 121 | }) 122 | electronProcess.stderr.on('data', data => { 123 | electronLog(data, 'red') 124 | }) 125 | 126 | electronProcess.on('close', () => { 127 | if (!manualRestart) process.exit() 128 | }) 129 | } 130 | 131 | function electronLog (data, color) { 132 | let log = '' 133 | data = data.toString().split(/\r?\n/) 134 | data.forEach(line => { 135 | log += ` ${line}\n` 136 | }) 137 | if (/[0-9A-z]+/.test(log)) { 138 | console.log( 139 | chalk[color].bold('┏ Electron -------------------') + 140 | '\n\n' + 141 | log + 142 | chalk[color].bold('┗ ----------------------------') + 143 | '\n' 144 | ) 145 | } 146 | } 147 | 148 | function greeting () { 149 | const cols = process.stdout.columns 150 | let text = '' 151 | 152 | if (cols > 104) text = 'electron-vue' 153 | else if (cols > 76) text = 'electron-|vue' 154 | else text = false 155 | 156 | if (text) { 157 | say(text, { 158 | colors: ['yellow'], 159 | font: 'simple3d', 160 | space: false 161 | }) 162 | } else console.log(chalk.yellow.bold('\n electron-vue')) 163 | console.log(chalk.blue(' getting ready...') + '\n') 164 | } 165 | 166 | function init () { 167 | greeting() 168 | 169 | Promise.all([startRenderer(), startMain()]) 170 | .then(() => { 171 | startElectron() 172 | }) 173 | .catch(err => { 174 | console.error(err) 175 | }) 176 | } 177 | 178 | init() 179 | -------------------------------------------------------------------------------- /.electron-vue/webpack.main.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'main' 4 | 5 | const path = require('path') 6 | const { dependencies } = require('../package.json') 7 | const webpack = require('webpack') 8 | 9 | const BabiliWebpackPlugin = require('babili-webpack-plugin') 10 | 11 | let mainConfig = { 12 | entry: { 13 | main: path.join(__dirname, '../src/main/index.js'), 14 | worker: path.join(__dirname, '../src/main/worker.js') 15 | }, 16 | externals: [ 17 | ...Object.keys(dependencies || {}) 18 | ], 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.(js)$/, 23 | enforce: 'pre', 24 | exclude: /node_modules/, 25 | use: { 26 | loader: 'eslint-loader', 27 | options: { 28 | formatter: require('eslint-friendly-formatter') 29 | } 30 | } 31 | }, 32 | { 33 | test: /\.js$/, 34 | use: 'babel-loader', 35 | exclude: /node_modules/ 36 | }, 37 | { 38 | test: /\.node$/, 39 | use: 'node-loader' 40 | } 41 | ] 42 | }, 43 | node: { 44 | __dirname: process.env.NODE_ENV !== 'production', 45 | __filename: process.env.NODE_ENV !== 'production' 46 | }, 47 | output: { 48 | filename: '[name].js', 49 | libraryTarget: 'commonjs2', 50 | path: path.join(__dirname, '../dist/electron') 51 | }, 52 | plugins: [ 53 | new webpack.NoEmitOnErrorsPlugin() 54 | ], 55 | resolve: { 56 | extensions: ['.js', '.json', '.node'] 57 | }, 58 | target: 'electron-main' 59 | } 60 | 61 | /** 62 | * Adjust mainConfig for development settings 63 | */ 64 | if (process.env.NODE_ENV !== 'production') { 65 | mainConfig.plugins.push( 66 | new webpack.DefinePlugin({ 67 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"` 68 | }) 69 | ) 70 | } 71 | 72 | /** 73 | * Adjust mainConfig for production settings 74 | */ 75 | if (process.env.NODE_ENV === 'production') { 76 | mainConfig.plugins.push( 77 | new BabiliWebpackPlugin(), 78 | new webpack.DefinePlugin({ 79 | 'process.env.NODE_ENV': '"production"' 80 | }) 81 | ) 82 | } 83 | 84 | module.exports = mainConfig 85 | -------------------------------------------------------------------------------- /.electron-vue/webpack.renderer.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'renderer' 4 | 5 | const path = require('path') 6 | const { dependencies } = require('../package.json') 7 | const webpack = require('webpack') 8 | 9 | const BabiliWebpackPlugin = require('babili-webpack-plugin') 10 | const CopyWebpackPlugin = require('copy-webpack-plugin') 11 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 12 | const HtmlWebpackPlugin = require('html-webpack-plugin') 13 | 14 | /** 15 | * List of node_modules to include in webpack bundle 16 | * 17 | * Required for specific packages like Vue UI libraries 18 | * that provide pure *.vue files that need compiling 19 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals 20 | */ 21 | let whiteListedModules = ['vue'] 22 | 23 | let rendererConfig = { 24 | devtool: '#cheap-module-eval-source-map', 25 | entry: { 26 | renderer: path.join(__dirname, '../src/renderer/main.js') 27 | }, 28 | externals: [ 29 | ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)) 30 | ], 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.(js|vue)$/, 35 | enforce: 'pre', 36 | exclude: /node_modules/, 37 | use: { 38 | loader: 'eslint-loader', 39 | options: { 40 | formatter: require('eslint-friendly-formatter') 41 | } 42 | } 43 | }, 44 | { 45 | test: /\.css$/, 46 | use: ExtractTextPlugin.extract({ 47 | fallback: 'style-loader', 48 | use: 'css-loader' 49 | }) 50 | }, 51 | { 52 | test: /\.html$/, 53 | use: 'vue-html-loader' 54 | }, 55 | { 56 | test: /\.js$/, 57 | use: 'babel-loader', 58 | exclude: /node_modules/ 59 | }, 60 | { 61 | test: /\.node$/, 62 | use: 'node-loader' 63 | }, 64 | { 65 | test: /\.vue$/, 66 | use: { 67 | loader: 'vue-loader', 68 | options: { 69 | extractCSS: process.env.NODE_ENV === 'production', 70 | loaders: { 71 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1', 72 | scss: 'vue-style-loader!css-loader!sass-loader' 73 | } 74 | } 75 | } 76 | }, 77 | { 78 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 79 | use: { 80 | loader: 'url-loader', 81 | query: { 82 | limit: 10000, 83 | name: 'imgs/[name]--[folder].[ext]' 84 | } 85 | } 86 | }, 87 | { 88 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 89 | loader: 'url-loader', 90 | options: { 91 | limit: 10000, 92 | name: 'media/[name]--[folder].[ext]' 93 | } 94 | }, 95 | { 96 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 97 | use: { 98 | loader: 'url-loader', 99 | query: { 100 | limit: 10000, 101 | name: 'fonts/[name]--[folder].[ext]' 102 | } 103 | } 104 | } 105 | ] 106 | }, 107 | node: { 108 | __dirname: process.env.NODE_ENV !== 'production', 109 | __filename: process.env.NODE_ENV !== 'production' 110 | }, 111 | plugins: [ 112 | new ExtractTextPlugin('styles.css'), 113 | new HtmlWebpackPlugin({ 114 | filename: 'index.html', 115 | template: path.resolve(__dirname, '../src/index.ejs'), 116 | minify: { 117 | collapseWhitespace: true, 118 | removeAttributeQuotes: true, 119 | removeComments: true 120 | }, 121 | nodeModules: process.env.NODE_ENV !== 'production' 122 | ? path.resolve(__dirname, '../node_modules') 123 | : false 124 | }), 125 | new webpack.HotModuleReplacementPlugin(), 126 | new webpack.NoEmitOnErrorsPlugin() 127 | ], 128 | output: { 129 | filename: '[name].js', 130 | libraryTarget: 'commonjs2', 131 | path: path.join(__dirname, '../dist/electron') 132 | }, 133 | resolve: { 134 | alias: { 135 | '@': path.join(__dirname, '../src/renderer'), 136 | 'vue$': 'vue/dist/vue.esm.js' 137 | }, 138 | extensions: ['.js', '.vue', '.json', '.css', '.node'] 139 | }, 140 | target: 'electron-renderer' 141 | } 142 | 143 | /** 144 | * Adjust rendererConfig for development settings 145 | */ 146 | if (process.env.NODE_ENV !== 'production') { 147 | rendererConfig.plugins.push( 148 | new webpack.DefinePlugin({ 149 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"` 150 | }) 151 | ) 152 | } 153 | 154 | /** 155 | * Adjust rendererConfig for production settings 156 | */ 157 | if (process.env.NODE_ENV === 'production') { 158 | rendererConfig.devtool = '' 159 | 160 | rendererConfig.plugins.push( 161 | new BabiliWebpackPlugin(), 162 | new CopyWebpackPlugin([ 163 | { 164 | from: path.join(__dirname, '../static'), 165 | to: path.join(__dirname, '../dist/electron/static'), 166 | ignore: ['.*'] 167 | } 168 | ]), 169 | new webpack.DefinePlugin({ 170 | 'process.env.NODE_ENV': '"production"' 171 | }), 172 | new webpack.LoaderOptionsPlugin({ 173 | minimize: true 174 | }) 175 | ) 176 | } 177 | 178 | module.exports = rendererConfig 179 | -------------------------------------------------------------------------------- /.electron-vue/webpack.web.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'web' 4 | 5 | const path = require('path') 6 | const webpack = require('webpack') 7 | 8 | const BabiliWebpackPlugin = require('babili-webpack-plugin') 9 | const CopyWebpackPlugin = require('copy-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const HtmlWebpackPlugin = require('html-webpack-plugin') 12 | 13 | let webConfig = { 14 | devtool: '#cheap-module-eval-source-map', 15 | entry: { 16 | web: path.join(__dirname, '../src/renderer/main.js') 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.(js|vue)$/, 22 | enforce: 'pre', 23 | exclude: /node_modules/, 24 | use: { 25 | loader: 'eslint-loader', 26 | options: { 27 | formatter: require('eslint-friendly-formatter') 28 | } 29 | } 30 | }, 31 | { 32 | test: /\.css$/, 33 | use: ExtractTextPlugin.extract({ 34 | fallback: 'style-loader', 35 | use: 'css-loader' 36 | }) 37 | }, 38 | { 39 | test: /\.html$/, 40 | use: 'vue-html-loader' 41 | }, 42 | { 43 | test: /\.js$/, 44 | use: 'babel-loader', 45 | include: [ path.resolve(__dirname, '../src/renderer') ], 46 | exclude: /node_modules/ 47 | }, 48 | { 49 | test: /\.vue$/, 50 | use: { 51 | loader: 'vue-loader', 52 | options: { 53 | extractCSS: true, 54 | loaders: { 55 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1', 56 | scss: 'vue-style-loader!css-loader!sass-loader' 57 | } 58 | } 59 | } 60 | }, 61 | { 62 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 63 | use: { 64 | loader: 'url-loader', 65 | query: { 66 | limit: 10000, 67 | name: 'imgs/[name].[ext]' 68 | } 69 | } 70 | }, 71 | { 72 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 73 | use: { 74 | loader: 'url-loader', 75 | query: { 76 | limit: 10000, 77 | name: 'fonts/[name].[ext]' 78 | } 79 | } 80 | } 81 | ] 82 | }, 83 | plugins: [ 84 | new ExtractTextPlugin('styles.css'), 85 | new HtmlWebpackPlugin({ 86 | filename: 'index.html', 87 | template: path.resolve(__dirname, '../src/index.ejs'), 88 | minify: { 89 | collapseWhitespace: true, 90 | removeAttributeQuotes: true, 91 | removeComments: true 92 | }, 93 | nodeModules: false 94 | }), 95 | new webpack.DefinePlugin({ 96 | 'process.env.IS_WEB': 'true' 97 | }), 98 | new webpack.HotModuleReplacementPlugin(), 99 | new webpack.NoEmitOnErrorsPlugin() 100 | ], 101 | output: { 102 | filename: '[name].js', 103 | path: path.join(__dirname, '../dist/web') 104 | }, 105 | resolve: { 106 | alias: { 107 | '@': path.join(__dirname, '../src/renderer'), 108 | 'vue$': 'vue/dist/vue.esm.js' 109 | }, 110 | extensions: ['.js', '.vue', '.json', '.css'] 111 | }, 112 | target: 'web' 113 | } 114 | 115 | /** 116 | * Adjust webConfig for production settings 117 | */ 118 | if (process.env.NODE_ENV === 'production') { 119 | webConfig.devtool = '' 120 | 121 | webConfig.plugins.push( 122 | new BabiliWebpackPlugin(), 123 | new CopyWebpackPlugin([ 124 | { 125 | from: path.join(__dirname, '../static'), 126 | to: path.join(__dirname, '../dist/web/static'), 127 | ignore: ['.*'] 128 | } 129 | ]), 130 | new webpack.DefinePlugin({ 131 | 'process.env.NODE_ENV': '"production"' 132 | }), 133 | new webpack.LoaderOptionsPlugin({ 134 | minimize: true 135 | }) 136 | ) 137 | } 138 | 139 | module.exports = webConfig 140 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/.eslintignore -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true, 9 | node: true 10 | }, 11 | extends: 'standard', 12 | globals: { 13 | __static: true 14 | }, 15 | plugins: [ 16 | 'html' 17 | ], 18 | 'rules': { 19 | // allow paren-less arrow functions 20 | 'arrow-parens': 0, 21 | // allow async-await 22 | 'generator-star-spacing': 0, 23 | // allow debugger during development 24 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist/electron/* 3 | dist/web/* 4 | build/* 5 | !build/icons 6 | node_modules/ 7 | npm-debug.log 8 | npm-debug.log.* 9 | thumbs.db 10 | !.gitkeep 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode8.3 2 | sudo: required 3 | dist: trusty 4 | language: c 5 | matrix: 6 | include: 7 | - os: osx 8 | - os: linux 9 | env: CC=clang CXX=clang++ npm_config_clang=1 10 | compiler: clang 11 | cache: 12 | directories: 13 | - node_modules 14 | - "$HOME/.electron" 15 | - "$HOME/.cache" 16 | addons: 17 | apt: 18 | packages: 19 | - libgnome-keyring-dev 20 | - icnsutils 21 | before_install: 22 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils; fi 23 | install: 24 | - nvm install 7 25 | - curl -o- -L https://yarnpkg.com/install.sh | bash 26 | - source ~/.bashrc 27 | - npm install -g xvfb-maybe 28 | - yarn 29 | script: 30 | - yarn run build 31 | branches: 32 | only: 33 | - master 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jonathan Soma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Python Wrangler 2 | 3 | Clean up all those Pythons crawling around your computer 4 | 5 | **Python Wrangler** helps you install Python in a nice, pleasant, perfect way. Already installed it? Four times? Eighteen times? No problem, Python Wrangler can also help you **understand, organize, and remove** the Python installations hiding on your computer. 6 | 7 | It’s **highly opinionated**, so you don’t need to think too hard: just do what Python Wrangler says and you’ll end up with a nice setup. Have opinions of your own? No worries, just use the data it gives you to clean up your system according to your own best practices. 8 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | - /v\d*\.\d*\.\d*/ 5 | 6 | image: Visual Studio 2017 7 | platform: 8 | - x64 9 | 10 | cache: 11 | - node_modules 12 | - '%APPDATA%\npm-cache' 13 | - '%USERPROFILE%\.electron' 14 | - '%USERPROFILE%\AppData\Local\Yarn\cache' 15 | 16 | init: 17 | - git config --global core.autocrlf input 18 | 19 | install: 20 | - ps: Install-Product node 8 x64 21 | - choco install yarn --ignore-dependencies 22 | - git reset --hard HEAD 23 | - yarn 24 | - node --version 25 | 26 | build_script: 27 | - yarn build 28 | 29 | test: off 30 | -------------------------------------------------------------------------------- /build/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/build/icons/256x256.png -------------------------------------------------------------------------------- /build/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/build/icons/icon.icns -------------------------------------------------------------------------------- /build/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/build/icons/icon.ico -------------------------------------------------------------------------------- /icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/icons/icon.icns -------------------------------------------------------------------------------- /icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/icons/icon.ico -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "python-wrangler", 3 | "version": "0.3.0", 4 | "homepage": "http://littlecolumns.com/tools/python-wrangler/", 5 | "author": "Jonathan Soma ", 6 | "description": "Clean up all those Pythons crawling around your computer", 7 | "license": "mit", 8 | "main": "./dist/electron/main.js", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/littlecolumns/python-wrangler.git" 12 | }, 13 | "scripts": { 14 | "build": "node .electron-vue/build.js && electron-builder", 15 | "build:dir": "node .electron-vue/build.js && electron-builder --dir", 16 | "build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js", 17 | "build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js", 18 | "dev": "node .electron-vue/dev-runner.js", 19 | "e2e": "npm run pack && mocha test/e2e", 20 | "lint": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter src test", 21 | "lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter --fix src test", 22 | "pack": "npm run pack:main && npm run pack:renderer", 23 | "pack:main": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js", 24 | "pack:renderer": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js", 25 | "test": "npm run unit && npm run e2e", 26 | "unit": "karma start test/unit/karma.conf.js", 27 | "postinstall": "npm run lint:fix" 28 | }, 29 | "build": { 30 | "productName": "Python Wrangler", 31 | "appId": "com.littlecolumns.python-wrangler", 32 | "directories": { 33 | "output": "build" 34 | }, 35 | "files": [ 36 | "dist/electron/**/*" 37 | ], 38 | "dmg": { 39 | "contents": [ 40 | { 41 | "x": 410, 42 | "y": 150, 43 | "type": "link", 44 | "path": "/Applications" 45 | }, 46 | { 47 | "x": 130, 48 | "y": 150, 49 | "type": "file" 50 | } 51 | ] 52 | }, 53 | "mac": { 54 | "icon": "build/icons/icon.icns" 55 | }, 56 | "win": { 57 | "icon": "build/icons/icon.ico" 58 | }, 59 | "linux": { 60 | "icon": "build/icons" 61 | } 62 | }, 63 | "dependencies": { 64 | "@fortawesome/fontawesome": "^1.1.5", 65 | "@fortawesome/fontawesome-free-solid": "^5.0.9", 66 | "@fortawesome/vue-fontawesome": "^0.0.22", 67 | "axios": "^0.16.1", 68 | "bulma": "^0.6.2", 69 | "electron-log": "^2.2.14", 70 | "file-saver": "^1.3.8", 71 | "fix-path": "^2.1.0", 72 | "node-sass": "^4.8.3", 73 | "sass-loader": "^6.0.7", 74 | "shelljs": "^0.8.1", 75 | "vue": "^2.3.3", 76 | "vue-electron": "^1.0.6", 77 | "vue-resource": "^1.5.0", 78 | "vue-router": "^2.5.3", 79 | "vuex": "^2.3.1", 80 | "winreg": "^1.2.4" 81 | }, 82 | "devDependencies": { 83 | "babel-core": "^6.25.0", 84 | "babel-eslint": "^7.2.3", 85 | "babel-loader": "^7.1.1", 86 | "babel-plugin-transform-runtime": "^6.23.0", 87 | "babel-preset-env": "^1.6.0", 88 | "babel-preset-stage-0": "^6.24.1", 89 | "babel-register": "^6.24.1", 90 | "babili-webpack-plugin": "^0.1.2", 91 | "cfonts": "^1.1.3", 92 | "chalk": "^2.1.0", 93 | "copy-webpack-plugin": "^4.0.1", 94 | "cross-env": "^5.0.5", 95 | "css-loader": "^0.28.4", 96 | "del": "^3.0.0", 97 | "devtron": "^1.4.0", 98 | "electron": "^1.7.5", 99 | "electron-debug": "^1.4.0", 100 | "electron-devtools-installer": "^2.2.0", 101 | "electron-builder": "^19.19.1", 102 | "eslint": "^4.4.1", 103 | "eslint-config-standard": "^10.2.1", 104 | "eslint-friendly-formatter": "^3.0.0", 105 | "eslint-loader": "^1.9.0", 106 | "eslint-plugin-html": "^3.1.1", 107 | "eslint-plugin-import": "^2.7.0", 108 | "eslint-plugin-node": "^5.1.1", 109 | "eslint-plugin-promise": "^3.5.0", 110 | "eslint-plugin-standard": "^3.0.1", 111 | "extract-text-webpack-plugin": "^3.0.0", 112 | "file-loader": "^0.11.2", 113 | "html-webpack-plugin": "^2.30.1", 114 | "inject-loader": "^3.0.0", 115 | "karma": "^1.3.0", 116 | "karma-chai": "^0.1.0", 117 | "karma-coverage": "^1.1.1", 118 | "karma-electron": "^5.1.1", 119 | "karma-mocha": "^1.2.0", 120 | "karma-sourcemap-loader": "^0.3.7", 121 | "karma-spec-reporter": "^0.0.31", 122 | "karma-webpack": "^2.0.1", 123 | "webpack-merge": "^4.1.0", 124 | "require-dir": "^0.3.0", 125 | "spectron": "^3.7.1", 126 | "babel-plugin-istanbul": "^4.1.1", 127 | "chai": "^4.0.0", 128 | "mocha": "^3.0.2", 129 | "multispinner": "^0.2.1", 130 | "node-loader": "^0.6.0", 131 | "style-loader": "^0.18.2", 132 | "url-loader": "^0.5.9", 133 | "vue-html-loader": "^1.2.4", 134 | "vue-loader": "^13.0.5", 135 | "vue-style-loader": "^3.0.1", 136 | "vue-template-compiler": "^2.4.2", 137 | "webpack": "^3.5.2", 138 | "webpack-dev-server": "^2.7.1", 139 | "webpack-hot-middleware": "^2.18.2" 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Python Wrangler 6 | <% if (htmlWebpackPlugin.options.nodeModules) { %> 7 | 8 | 11 | <% } %> 12 | 13 | 14 |
15 | 16 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/executable/executable.js: -------------------------------------------------------------------------------- 1 | import pathLib from 'path' 2 | import os from 'os' 3 | import glob from 'glob' 4 | import fs from 'fs' 5 | import cp from 'child_process' 6 | import { which } from 'shelljs' 7 | import trueCasePathSync from 'true-case-path' 8 | 9 | const EXTENSION_GLOB = '?(.exe|.bat)' 10 | 11 | const SEARCH_PATHS = [ 12 | '/usr/local/Cellar/python*/*/bin', 13 | pathLib.join(os.homedir(), '*conda*'), 14 | pathLib.join(os.homedir(), '*conda*', 'bin'), 15 | pathLib.join(os.homedir(), '*conda*', 'Scripts'), 16 | '/usr/local/bin', 17 | '/usr/bin', 18 | '/Library/Frameworks/Python.framework/Versions/*/bin', 19 | '/System/Library/Frameworks/Python.framework/Versions/*/bin', 20 | '/*conda*', 21 | '/*conda*/bin', 22 | '/*conda*/Scripts', 23 | '/*ython*', 24 | '/*ython*/Scripts', 25 | pathLib.join(os.homedir(), '.pyenv', 'shims'), 26 | pathLib.join(os.homedir(), '.pyenv', 'versions', '**', 'bin'), 27 | pathLib.join(os.homedir(), '*ython*'), 28 | pathLib.join(os.homedir(), '*ython*', 'Scripts') 29 | ].map(pathLib.normalize) 30 | 31 | function upOne (path) { 32 | return pathLib.normalize(pathLib.join(path, '..')) 33 | } 34 | 35 | function standardizePath (path) { 36 | try { 37 | return trueCasePathSync(path) 38 | } catch (err) { 39 | // It's missing, maybe? Doesn't really matter. 40 | return path 41 | } 42 | } 43 | 44 | class Executable { 45 | constructor (path) { 46 | this.errors = [] 47 | this.missing = false 48 | this.symlinks = [] 49 | 50 | this.path = standardizePath(path.trim()) 51 | this.setRealPath(this.path) 52 | 53 | this.setBasePath() 54 | 55 | this.pathLocations = [] 56 | this.defaultCommands = [] 57 | this.version = null 58 | this.rawVersion = null 59 | } 60 | 61 | populate () { 62 | let promises = [ 63 | this.setVersion(), 64 | this.setStats() 65 | ] 66 | return Promise.all(promises) 67 | .then(() => this) 68 | .catch(error => this.addError(error)) 69 | } 70 | 71 | exec (params) { 72 | if (this.missing) { 73 | return Promise.resolve({ stdout: '', stderr: '', all: '' }) 74 | } 75 | let execParams = typeof params === 'string' ? [params] : params 76 | return new Promise((resolve, reject) => { 77 | cp.execFile( 78 | this.path, 79 | execParams, 80 | // { shell: true }, 81 | (error, stdout, stderr) => { 82 | let stdoutCleaned = (stdout || '').trim() 83 | let stderrCleaned = (stderr || '').trim() 84 | resolve({ 85 | error: error, 86 | stdout: stdoutCleaned, 87 | stderr: stderrCleaned, 88 | all: stdoutCleaned + stderrCleaned 89 | }) 90 | } 91 | ) 92 | }) 93 | } 94 | 95 | addError (error) { 96 | this.errors.push({ 97 | error: error, 98 | message: error.message, 99 | stack: error.stack 100 | }) 101 | return this 102 | } 103 | 104 | merge (executable) { 105 | executable.symlinks.forEach(this.addSymlink, this) 106 | executable.defaultCommands.forEach(this.addDefaultCommand, this) 107 | executable.pathLocations.forEach(this.addPathLocation, this) 108 | } 109 | 110 | addPathLocation (path) { 111 | if (this.pathLocations.indexOf(path) === -1) { 112 | this.pathLocations.push(path) 113 | } 114 | } 115 | 116 | addDefaultCommand (command) { 117 | if (this.defaultCommands.indexOf(command) === -1) { 118 | this.defaultCommands.push(command) 119 | } 120 | } 121 | 122 | learnPathLocations (paths) { 123 | paths.forEach((path, i) => { 124 | path = standardizePath(path) 125 | if (path === this.path || this.symlinks.indexOf(path) !== -1) { 126 | this.addPathLocation(path) 127 | if (i === 0) { 128 | this.addDefaultCommand(pathLib.basename(path)) 129 | } 130 | } 131 | }) 132 | } 133 | 134 | setRawVersion () { 135 | return this.exec('--version') 136 | .then((output) => { 137 | this.rawVersion = output.all 138 | }) 139 | } 140 | 141 | setVersion () { 142 | return this.setRawVersion() 143 | .then(() => { 144 | let match = this.rawVersion.match(/\d[\w.]*/i) 145 | if (match) { 146 | this.version = match[0] 147 | } 148 | }) 149 | } 150 | 151 | setBasePath () { 152 | this.basepath = upOne(this.path) 153 | if (['bin', 'Scripts'].indexOf(pathLib.basename(this.basepath)) !== -1) { 154 | this.basepath = upOne(this.basepath) 155 | } 156 | } 157 | 158 | /** 159 | * The path of the executable, or the target of a symlinked executable 160 | * @return {string} The executable file's path 161 | */ 162 | setRealPath (path) { 163 | let realpath 164 | 165 | try { 166 | let realpath = pathLib.resolve(pathLib.dirname(path), fs.readlinkSync(path)) 167 | this.addSymlink(path) 168 | this.path = realpath 169 | } catch (error) { 170 | // It's not a symlink 171 | } 172 | 173 | if (realpath) { 174 | /* But maybe that link is wrong! This throws an error 175 | if the target doesn't exist, though, which is why 176 | we try th eother one version first. */ 177 | try { 178 | this.path = fs.realpathSync(path) 179 | } catch (error) { 180 | this.addError(error) 181 | } 182 | } 183 | 184 | return this.path 185 | } 186 | 187 | /** 188 | * Query for the executable file's creation/modification/access time 189 | * and save it to the object 190 | * @return {Promise} 191 | */ 192 | setStats () { 193 | return new Promise((resolve, reject) => { 194 | fs.stat(this.path, (error, stats) => { 195 | if (error) { 196 | this.missing = true 197 | this.addError(error) 198 | } else { 199 | this.atime = stats.atime.getTime() / 1000 200 | this.ctime = stats.ctime.getTime() / 1000 201 | this.mtime = stats.mtime.getTime() / 1000 202 | this.size = stats.size 203 | } 204 | resolve(this) 205 | }) 206 | }) 207 | } 208 | 209 | addSymlink (path) { 210 | if (path !== '' && this.symlinks.indexOf(path) === -1) { 211 | this.symlinks.push(path) 212 | } 213 | } 214 | 215 | sameAs (executable) { 216 | if (this.path === executable.path) { 217 | return true 218 | } 219 | if (executable.symlinks.indexOf(this.path) !== -1) { 220 | return true 221 | } 222 | if (this.symlinks.indexOf(executable.path) !== -1) { 223 | return true 224 | } 225 | for (let i = 0; i < this.symlinks.length; i++) { 226 | for (let j = 0; j < executable.symlinks.length; j++) { 227 | if (this.symlinks[i] === executable.symlinks[j]) { 228 | return true 229 | } 230 | } 231 | } 232 | } 233 | 234 | static dedupe (executables) { 235 | let originals = [] 236 | 237 | executables.forEach(executable => { 238 | let found = false 239 | originals.forEach(original => { 240 | if (original.sameAs(executable)) { 241 | found = true 242 | original.merge(executable) 243 | } 244 | }) 245 | if (!found) { 246 | originals.push(executable) 247 | } 248 | }) 249 | 250 | return originals 251 | } 252 | 253 | static findInSearchPaths (command) { 254 | let promises = SEARCH_PATHS.map((searchPath) => { 255 | return new Promise((resolve, reject) => { 256 | glob.glob(pathLib.join(searchPath, command + EXTENSION_GLOB), (error, paths) => { 257 | if (error) { 258 | reject(error) 259 | } else { 260 | let executables = paths.map(path => new (this)(path)) 261 | resolve(executables) 262 | } 263 | }) 264 | }) 265 | }) 266 | 267 | return Promise.all(promises) 268 | } 269 | 270 | static findByWhich (command) { 271 | return which('-a', command) 272 | } 273 | 274 | static findAllMultipleCommands (commands, options = {}) { 275 | let promises = commands.map(command => this.findAll(command)) 276 | return Promise.all(promises) 277 | .then(found => [].concat(...found)) 278 | .then(executables => this.dedupe(executables)) 279 | .then(executables => { 280 | if (options.populate) { 281 | return Promise.all(executables.map(e => e.populate())) 282 | } else { 283 | return Promise.resolve(executables) 284 | } 285 | }) 286 | } 287 | 288 | static findAll (command, options = {}) { 289 | if (Array.isArray(command)) { 290 | return this.findAllMultipleCommands(command, options) 291 | } 292 | 293 | let locations = this.findByWhich(command) 294 | 295 | let executables = locations.map(path => new (this)(path)) 296 | 297 | return this.findInSearchPaths(command) 298 | .then(found => [].concat.apply(executables, found)) 299 | .then(executables => { 300 | executables.forEach(p => p.learnPathLocations(locations)) 301 | return this.dedupe(executables) 302 | }) 303 | .then(executables => { 304 | if (options.populate) { 305 | return Promise.all(executables.map(e => e.populate())) 306 | } else { 307 | return Promise.resolve(executables) 308 | } 309 | }) 310 | } 311 | } 312 | 313 | export default Executable 314 | -------------------------------------------------------------------------------- /src/main/executable/index.js: -------------------------------------------------------------------------------- 1 | import PythonExecutable from './python' 2 | import JupyterExecutable from './jupyter' 3 | import PyenvExecutable from './pyenv' 4 | import PipenvExecutable from './pipenv' 5 | 6 | export default { 7 | Python: PythonExecutable, 8 | Jupyter: JupyterExecutable, 9 | Pyenv: PyenvExecutable, 10 | Pipenv: PipenvExecutable 11 | } 12 | -------------------------------------------------------------------------------- /src/main/executable/jupyter.js: -------------------------------------------------------------------------------- 1 | import Executable from './executable' 2 | 3 | class JupyterExecutable extends Executable { 4 | populate () { 5 | return Executable.prototype.populate.call(this) 6 | .then(() => this.detectKernels()) 7 | } 8 | 9 | detectKernels () { 10 | return this.exec(['kernelspec', 'list', '--json']) 11 | .then(output => { 12 | if (output.stdout.trim() !== '') { 13 | this.kernels = JSON.parse(output.stdout) 14 | } 15 | return this 16 | }) 17 | .catch(error => this.addError(error)) 18 | } 19 | } 20 | 21 | export default JupyterExecutable 22 | -------------------------------------------------------------------------------- /src/main/executable/pipenv.js: -------------------------------------------------------------------------------- 1 | import Executable from './executable' 2 | 3 | class PipenvExecutable extends Executable { 4 | } 5 | 6 | export default PipenvExecutable 7 | -------------------------------------------------------------------------------- /src/main/executable/pyenv.js: -------------------------------------------------------------------------------- 1 | import Executable from './executable' 2 | 3 | class PyenvExecutable extends Executable { 4 | constructor (path) { 5 | super(path) 6 | this.pythonVersions = [] 7 | this.defaultPythonVersion = null 8 | } 9 | 10 | populate () { 11 | return Executable.prototype.populate.call(this) 12 | .then(() => this.setRoot()) 13 | .then(() => this.detectPythons()) 14 | .then(() => this) 15 | .catch(error => this.addError(error)) 16 | } 17 | 18 | setRoot () { 19 | return this.exec(['root']) 20 | .then(output => { 21 | this.root = output.stdout 22 | }) 23 | } 24 | 25 | detectPythons () { 26 | return this.exec(['versions']) 27 | .then(output => { 28 | output.stdout.split('\n') 29 | .forEach(line => { 30 | let version = line.replace(/^[^\w]*/, '').split(' ')[0].trim() 31 | let isDefault = line.indexOf('*') !== -1 32 | this.addPythonVersion(version, isDefault) 33 | }) 34 | }) 35 | } 36 | 37 | addPythonVersion (version, isDefault) { 38 | if (this.pythonVersions.indexOf(version) === -1) { 39 | this.pythonVersions.push(version) 40 | } 41 | if (isDefault) { 42 | this.defaultPythonVersion = version 43 | } 44 | } 45 | 46 | detectKernels () { 47 | return this.exec(['kernelspec', 'list', '--json']) 48 | .then(output => { 49 | if (output.stdout.trim() !== '') { 50 | this.kernels = JSON.parse(output.stdout) 51 | } 52 | return this 53 | }) 54 | .catch(error => this.addError(error)) 55 | } 56 | } 57 | 58 | export default PyenvExecutable 59 | -------------------------------------------------------------------------------- /src/main/executable/python.js: -------------------------------------------------------------------------------- 1 | import Executable from './executable' 2 | 3 | class PythonExecutable extends Executable { 4 | constructor (path) { 5 | super(path) 6 | this.installer = null 7 | this.packageOvercount = false 8 | this.packages = null 9 | } 10 | 11 | setPackages () { 12 | return this.exec(['-m', 'pip', 'list', '--format=legacy']) 13 | .then((output) => { 14 | if (output.stdout !== '') { 15 | this.packages = output.stdout.split('\n') 16 | .filter(line => line !== '') 17 | .map(line => line.match(/(.+) \((.*)\)/)) 18 | .map(match => ({ 19 | name: match[1], 20 | version: match[2] 21 | })) 22 | return this 23 | } else { 24 | return this.exec(['-c', 'import pkgutil; print(\':::\'.join(sorted([name for imp, name, ispkg in pkgutil.iter_modules()])))']) 25 | .then((output) => { 26 | this.packages = output.stdout.split(':::') 27 | .filter(line => line !== '') 28 | .map(line => ({'name': line})) 29 | this.packageOvercount = true 30 | return this 31 | }) 32 | } 33 | }) 34 | .catch(error => this.addError(error)) 35 | } 36 | 37 | setSysPath () { 38 | return this.exec(['-c', 'import sys; print(\':::\'.join([path for path in sys.path if path]))']) 39 | .then((output) => { 40 | this.syspath = output.stdout 41 | .split(':::') 42 | .map(path => path.trim()) 43 | .filter(path => path !== '') 44 | return this 45 | }) 46 | .catch(error => this.addError(error)) 47 | } 48 | 49 | setVersion () { 50 | return Executable.prototype.setVersion.call(this) 51 | .then(() => { 52 | this.baseVersion = Math.floor(parseInt(this.version)) 53 | }) 54 | } 55 | 56 | populate () { 57 | return Executable.prototype.populate.call(this) 58 | .then(() => this.setSysPath()) 59 | .then(() => this.detectInstaller()) 60 | .then(() => this) 61 | .catch(error => this.addError(error)) 62 | } 63 | 64 | detectInstaller () { 65 | let detectives = [ 66 | ['version', 'anaconda', 'Anaconda'], 67 | ['version', 'anaconda', 'Continuum'], 68 | ['path', 'pythonorg', '/Library/Frameworks/Python.framework/Versions/2.7'], 69 | ['path', 'pythonorg', '/Library/Frameworks/Python.framework/Versions/3.6'], 70 | ['path', 'pythonorg', '/Library/Frameworks/Python.framework/Versions/3.7'], 71 | ['path', 'pythonorg', '/Library/Frameworks/Python.framework/Versions/3.8'], 72 | ['path', 'pythonorg', '/Library/Frameworks/Python.framework/Versions/3.9'], 73 | ['path', 'pipenv', 'local/share/virtualenvs'], 74 | ['path', 'canopy', 'Enthought'], 75 | ['path', 'canopy', 'Canopy'], 76 | ['path', 'anaconda', 'anaconda'], 77 | ['path', 'miniconda', 'miniconda'], 78 | ['path', 'homebrew', 'Cellar'], 79 | ['path', 'pyenv', 'pyenv'], 80 | ['path', 'pythonorg', 'APPDATA\\LOCAL\\PROGRAMS\\PYTHON'] 81 | ] 82 | 83 | let index = detectives.findIndex(d => { 84 | if (d[0] === 'version' && this.version) { 85 | return this.version.indexOf(d[2]) !== -1 86 | } if (d[0] === 'path' && this.path) { 87 | return this.path.indexOf(d[2]) !== -1 88 | } 89 | }) 90 | 91 | if (index !== -1) { 92 | this.installer = detectives[index][1] 93 | return Promise.resolve(this) 94 | } else { 95 | return this.detectInstallerInDepth() 96 | } 97 | } 98 | 99 | detectInstallerInDepth () { 100 | var detectives = [ 101 | this.isActivePython(), 102 | this.isWinPython(), 103 | this.isXyPython() 104 | ] 105 | return Promise.all(detectives) 106 | .then(responses => { 107 | this.installer = responses[0] ? 'activepython' 108 | : responses[1] ? 'winpython' 109 | : responses[2] ? 'xypython' 110 | : null 111 | return this 112 | }) 113 | .catch(error => this.addError(error)) 114 | } 115 | 116 | isActivePython () { 117 | return new Promise((resolve, reject) => { 118 | this.exec(['-c', 'import activestate']) 119 | .then(output => resolve(!output.error)) 120 | }) 121 | } 122 | 123 | isWinPython () { 124 | return new Promise((resolve, reject) => { 125 | this.exec(['-c', 'import winpython']) 126 | .then(output => resolve(!output.error)) 127 | }) 128 | } 129 | 130 | isXyPython () { 131 | return new Promise((resolve, reject) => { 132 | this.exec(['-m', 'pip', '--version']) 133 | .then(output => resolve(output.stdout.indexOf('xy') !== -1)) 134 | }) 135 | } 136 | } 137 | 138 | export default PythonExecutable 139 | -------------------------------------------------------------------------------- /src/main/index.dev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is used specifically and only for development. It installs 3 | * `electron-debug` & `vue-devtools`. There shouldn't be any need to 4 | * modify this file, but it can be used to extend your development 5 | * environment. 6 | */ 7 | 8 | /* eslint-disable */ 9 | 10 | // Set environment for development 11 | process.env.NODE_ENV = 'development' 12 | 13 | // Install `electron-debug` with `devtron` 14 | require('electron-debug')({ showDevTools: true }) 15 | 16 | // Install `vue-devtools` 17 | require('electron').app.on('ready', () => { 18 | let installExtension = require('electron-devtools-installer') 19 | installExtension.default(installExtension.VUEJS_DEVTOOLS) 20 | .then(() => {}) 21 | .catch(err => { 22 | console.log('Unable to install `vue-devtools`: \n', err) 23 | }) 24 | }) 25 | 26 | // Require `main` process to boot app 27 | require('./index') 28 | -------------------------------------------------------------------------------- /src/main/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { app, BrowserWindow, ipcMain, Menu, screen } from 'electron' 4 | import { join, resolve } from 'path' 5 | import { fork } from 'child_process' 6 | import log from 'electron-log' 7 | import os from 'os' 8 | 9 | log.transports.file.appName = 'Python Wrangler' 10 | log.transports.file.level = 'verbose' 11 | log.transports.console.level = false 12 | 13 | log.info('[HOST] Starting up') 14 | 15 | // let env = { ...process.env } 16 | 17 | // env['ELECTRON_RUN_AS_NODE'] = true 18 | // env['ELECTRON_NO_ASAR'] = true 19 | 20 | let workerPath 21 | if (process.env.NODE_ENV === 'production') { 22 | workerPath = resolve(join(__dirname, 'worker.js')) 23 | } else { 24 | workerPath = resolve(join(__dirname, '..', '..', 'dist', 'electron', 'worker.js')) 25 | } 26 | 27 | let worker = fork(workerPath) 28 | 29 | // let worker = spawn( 30 | // process.execPath, 31 | // [join(__dirname, '/worker.js')], 32 | // { env: env, stdio: [null, null, null, null, 'ipc'] } 33 | // ) 34 | 35 | /** 36 | * Set `__static` path to static files in production 37 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html 38 | */ 39 | if (process.env.NODE_ENV !== 'development') { 40 | global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\') 41 | } 42 | 43 | let mainWindow 44 | const winURL = process.env.NODE_ENV === 'development' 45 | ? `http://localhost:9080` 46 | : `file://${__dirname}/index.html` 47 | 48 | function setupCopyPaste () { 49 | let template = [{ 50 | label: 'Application', 51 | submenu: [ 52 | { label: 'About Application', selector: 'orderFrontStandardAboutPanel:' }, 53 | { type: 'separator' }, 54 | { label: 'Quit', accelerator: 'Command+Q', click: () => { app.quit() } } 55 | ] 56 | }, { 57 | label: 'Edit', 58 | submenu: [ 59 | { label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' }, 60 | { label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' }, 61 | { type: 'separator' }, 62 | { label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' }, 63 | { label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' }, 64 | { label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' }, 65 | { label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' } 66 | ] 67 | }] 68 | 69 | Menu.setApplicationMenu(Menu.buildFromTemplate(template)) 70 | } 71 | 72 | function createWindow () { 73 | /** 74 | * Initial window options 75 | */ 76 | const {width, height} = screen.getPrimaryDisplay().workAreaSize 77 | 78 | mainWindow = new BrowserWindow({ 79 | height: height * 0.80, 80 | width: width * 0.75, 81 | title: 'Python Wrangler' 82 | }) 83 | 84 | mainWindow.loadURL(winURL) 85 | 86 | mainWindow.on('closed', () => { 87 | mainWindow = null 88 | }) 89 | 90 | mainWindow.webContents.on('will-navigate', function (e, url) { 91 | e.preventDefault() 92 | require('electron').shell.openExternal(url) 93 | }) 94 | 95 | setupCopyPaste() 96 | } 97 | 98 | function logIncoming (m) { 99 | if (m.method !== 'get-packages') { 100 | log.info('[HOST RECV]', m.method, JSON.stringify(m.data, null, 2)) 101 | } else { 102 | log.info('[HOST RECV]', m.method, m.data.path) 103 | } 104 | } 105 | 106 | function setupCommunication () { 107 | // Receive results from child process 108 | worker.on('message', (m) => { 109 | logIncoming(m) 110 | try { 111 | if (mainWindow !== null) { 112 | mainWindow.webContents.send(m.method, m.data) 113 | } 114 | } catch (err) { 115 | log.info('Failed to send back to main window', err) 116 | } 117 | }) 118 | 119 | ipcMain.on('get-system', (event) => { 120 | log.info('[HOST] get-system') 121 | mainWindow.webContents.send('get-system', { 122 | system: { 123 | 'platform': os.platform(), 124 | 'type': os.type(), 125 | 'release': os.release(), 126 | 'shell': process.env.SHELL 127 | } 128 | }) 129 | }) 130 | 131 | ipcMain.on('get-pythons', (event) => { 132 | log.info('[HOST SEND] get-pythons') 133 | worker.send({'method': 'get-pythons'}) 134 | }) 135 | 136 | ipcMain.on('get-pyenvs', (event) => { 137 | worker.send({'method': 'get-pyenvs'}) 138 | }) 139 | 140 | ipcMain.on('get-jupyters', (event) => { 141 | worker.send({'method': 'get-jupyters'}) 142 | }) 143 | 144 | ipcMain.on('get-pipenvs', (event) => { 145 | worker.send({'method': 'get-pipenvs'}) 146 | }) 147 | 148 | ipcMain.on('get-packages', (event, path) => { 149 | worker.send({'method': 'get-packages', 'args': [path]}) 150 | }) 151 | } 152 | 153 | setupCommunication() 154 | 155 | worker.send({ method: 'hello' }) 156 | 157 | app.on('ready', createWindow) 158 | 159 | app.on('window-all-closed', () => { 160 | app.quit() 161 | worker.kill('SIGINT') 162 | }) 163 | 164 | app.on('activate', () => { 165 | if (mainWindow === null) { 166 | createWindow() 167 | } 168 | }) 169 | 170 | /** 171 | * Auto Updater 172 | * 173 | * Uncomment the following code below and install `electron-updater` to 174 | * support auto updating. Code Signing with a valid certificate is required. 175 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating 176 | */ 177 | 178 | /* 179 | import { autoUpdater } from 'electron-updater' 180 | 181 | autoUpdater.on('update-downloaded', () => { 182 | autoUpdater.quitAndInstall() 183 | }) 184 | 185 | app.on('ready', () => { 186 | if (process.env.NODE_ENV === 'production') autoUpdater.checkForUpdates() 187 | }) 188 | */ 189 | -------------------------------------------------------------------------------- /src/main/utils/case-converter.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // https://github.com/alexnm/json-style-converter/blob/master/es5/index.js 4 | 5 | var _typeof = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' ? function (obj) { return typeof obj } : function (obj) { return obj && typeof Symbol === 'function' && obj.constructor === Symbol && obj !== Symbol.prototype ? 'symbol' : typeof obj } 6 | 7 | function camelToSnakeCase (obj) { 8 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {} 9 | 10 | if (!isSimpleObject(options)) { 11 | return obj // avoiding String and other custom objects 12 | } 13 | 14 | if (typeof obj === 'string') { 15 | return camelToSnake(obj, options) 16 | } 17 | 18 | return traverse(obj, camelToSnake, options) 19 | } 20 | 21 | function traverse (obj, transform, options) { 22 | if (!obj) { 23 | return obj 24 | } 25 | 26 | if ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) !== 'object') { 27 | return obj // must be an object 28 | } 29 | 30 | if (Array.isArray(obj)) { 31 | return obj.map(function (el) { 32 | return traverse(el, transform, options) 33 | }) 34 | } 35 | 36 | if (!isSimpleObject(obj)) { 37 | return obj // avoiding String and other custom objects 38 | } 39 | 40 | return Object.keys(obj).reduce(function (acc, key) { 41 | var convertedKey = transform(key, options) 42 | acc[convertedKey] = traverse(obj[key], transform, options) 43 | return acc 44 | }, {}) 45 | } 46 | 47 | function isSimpleObject (obj) { 48 | return Object.prototype.toString.call(obj) === '[object Object]' 49 | } 50 | 51 | function camelToSnake (str, _ref) { 52 | var digitsAreUpperCase = _ref.digitsAreUpperCase 53 | 54 | var firstPass = str.replace(/[a-z][A-Z]/g, function (letters) { 55 | return letters[0] + '_' + letters[1].toLowerCase() 56 | }) 57 | if (digitsAreUpperCase) { 58 | return firstPass.replace(/[0-9]/g, function (digit) { 59 | return '_' + digit 60 | }) 61 | } 62 | 63 | return firstPass 64 | } 65 | 66 | export default camelToSnakeCase 67 | -------------------------------------------------------------------------------- /src/main/utils/refresh-path.js: -------------------------------------------------------------------------------- 1 | import { platform } from 'os' 2 | import Registry from 'winreg' 3 | import fixPath from 'fix-path' 4 | 5 | function getPath (reg) { 6 | return new Promise((resolve, reject) => { 7 | reg.values((err, items) => { 8 | if (err) { 9 | return resolve() 10 | } 11 | 12 | let paths = items.filter(item => item.name.toLowerCase() === 'path') 13 | if (paths.length === 0) { 14 | return resolve() 15 | } 16 | return resolve(paths[0].value) 17 | }) 18 | }) 19 | } 20 | 21 | function getWindowsPath () { 22 | let user = new Registry({ hive: Registry.HKCU, key: '\\Environment' }) 23 | let system = new Registry({ hive: Registry.HKLM, key: '\\System\\CurrentControlSet\\Control\\Session Manager\\Environment' }) 24 | 25 | return Promise.all([getPath(user), getPath(system)]) 26 | .then((results) => { 27 | let userPath = results[0] || '' 28 | let systemPath = results[1] || '' 29 | let path = (userPath.trim() + ';' + systemPath.trim()).replace(/;$/, '').replace(/^;/, '') 30 | return path.replace(/%(SystemRoot|SYSTEMROOT)%/g, process.env.SYSTEMROOT) 31 | }) 32 | .catch((e) => { 33 | return '' 34 | }) 35 | } 36 | 37 | function refreshPath () { 38 | if (platform() === 'win32') { 39 | return getWindowsPath() 40 | .then(path => { 41 | process.env.PATH = path 42 | }) 43 | } else { 44 | fixPath() 45 | return Promise.resolve() 46 | } 47 | } 48 | 49 | export default refreshPath 50 | -------------------------------------------------------------------------------- /src/main/worker.js: -------------------------------------------------------------------------------- 1 | import log from 'electron-log' 2 | import exe from './executable' 3 | import caseConverter from './utils/case-converter' 4 | import refreshPath from './utils/refresh-path' 5 | 6 | log.transports.file.appName = 'Python Wrangler' 7 | log.transports.file.level = 'verbose' 8 | log.transports.console.level = false 9 | 10 | const responses = { 11 | 'get-pythons' () { 12 | return exe.Python.findAll(['python', 'python3'], { populate: true }) 13 | .then(pythons => ({ pythons: pythons })) 14 | }, 15 | 'get-pyenvs' () { 16 | return exe.Pyenv.findAll(['pyenv'], { populate: true }) 17 | .then(pyenvs => { 18 | let pythonPaths = exe.Python.findByWhich('python') 19 | let activated = pythonPaths.length > 0 && pythonPaths[0].indexOf('pyenv') !== -1 20 | 21 | return { 22 | activated: activated, 23 | pyenvs: pyenvs 24 | } 25 | }) 26 | }, 27 | 'get-jupyters' () { 28 | return exe.Jupyter.findAll(['jupyter'], { populate: true }) 29 | .then(jupyters => ({ jupyters: jupyters })) 30 | }, 31 | 'get-pipenvs' () { 32 | return exe.Pipenv.findAll(['pipenv']) 33 | .then(pipenvs => ({ pipenvs: pipenvs })) 34 | }, 35 | 'hello' () { 36 | return Promise.resolve('hello back') 37 | }, 38 | 'get-packages' (path) { 39 | let python = new exe.Python(path) 40 | return python.setPackages() 41 | .then(python => { 42 | return { 43 | path: path, 44 | package_overcount: python.packageOvercount, 45 | packages: python.packages 46 | } 47 | }) 48 | } 49 | } 50 | 51 | process.on('message', function (msg) { 52 | log.info('[WORKER RECV]', msg) 53 | try { 54 | refreshPath() 55 | .then(() => responses[msg.method].apply(this, msg.args)) 56 | .then(data => { 57 | process.send({ 58 | method: msg.method, 59 | data: JSON.parse(JSON.stringify(caseConverter(data))) 60 | }) 61 | }) 62 | .catch(err => { 63 | log.error('[WORKER ERR]', err) 64 | process.send({ 65 | method: 'error', 66 | data: JSON.parse(JSON.stringify(err)) 67 | }) 68 | }) 69 | } catch (err) { 70 | log.error('[WORKER ERR]', err) 71 | process.send({ 72 | method: 'error', 73 | data: JSON.parse(JSON.stringify(err)) 74 | }) 75 | } 76 | }) 77 | -------------------------------------------------------------------------------- /src/renderer/App.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 25 | 26 | 212 | -------------------------------------------------------------------------------- /src/renderer/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/.gitkeep -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/HELP-US-OUT.txt: -------------------------------------------------------------------------------- 1 | I hope you love Font Awesome. If you've found it useful, please do me a favor and check out my latest project, 2 | Fort Awesome (https://fortawesome.com). It makes it easy to put the perfect icons on your website. Choose from our awesome, 3 | comprehensive icon sets or copy and paste your own. 4 | 5 | Please. Check it out. 6 | 7 | -Dave Gandy 8 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/font-awesome/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/less/animated.less: -------------------------------------------------------------------------------- 1 | // Animated Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .@{fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .@{fa-css-prefix}-pull-left { float: left; } 11 | .@{fa-css-prefix}-pull-right { float: right; } 12 | 13 | .@{fa-css-prefix} { 14 | &.@{fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.@{fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .@{fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "animated.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | @import "screen-reader.less"; 19 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | .fa-icon-rotate(@degrees, @rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})"; 16 | -webkit-transform: rotate(@degrees); 17 | -ms-transform: rotate(@degrees); 18 | transform: rotate(@degrees); 19 | } 20 | 21 | .fa-icon-flip(@horiz, @vert, @rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)"; 23 | -webkit-transform: scale(@horiz, @vert); 24 | -ms-transform: scale(@horiz, @vert); 25 | transform: scale(@horiz, @vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | .sr-only() { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | .sr-only-focusable() { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), 9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .@{fa-css-prefix}-rotate-90, 15 | :root .@{fa-css-prefix}-rotate-180, 16 | :root .@{fa-css-prefix}-rotate-270, 17 | :root .@{fa-css-prefix}-flip-horizontal, 18 | :root .@{fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/less/screen-reader.less: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { .sr-only(); } 5 | .sr-only-focusable { .sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/scss/_animated.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .#{$fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .#{$fa-css-prefix}-pull-left { float: left; } 11 | .#{$fa-css-prefix}-pull-right { float: right; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .#{$fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | @mixin fa-icon-rotate($degrees, $rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})"; 16 | -webkit-transform: rotate($degrees); 17 | -ms-transform: rotate($degrees); 18 | transform: rotate($degrees); 19 | } 20 | 21 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)"; 23 | -webkit-transform: scale($horiz, $vert); 24 | -ms-transform: scale($horiz, $vert); 25 | transform: scale($horiz, $vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | @mixin sr-only { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | @mixin sr-only-focusable { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), 9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/scss/_screen-reader.scss: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { @include sr-only(); } 5 | .sr-only-focusable { @include sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /src/renderer/assets/font-awesome/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "animated"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | @import "screen-reader"; 19 | -------------------------------------------------------------------------------- /src/renderer/assets/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Inconsolata-webfont'; 3 | src: url('fonts/inconsolata-webfont.woff2') format('woff2'), 4 | url('fonts/inconsolata-webfont.woff') format('woff'); 5 | font-weight: normal; 6 | font-style: normal; 7 | } -------------------------------------------------------------------------------- /src/renderer/assets/fonts/inconsolata-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/fonts/inconsolata-webfont.woff -------------------------------------------------------------------------------- /src/renderer/assets/fonts/inconsolata-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/fonts/inconsolata-webfont.woff2 -------------------------------------------------------------------------------- /src/renderer/assets/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/icons/128x128.png -------------------------------------------------------------------------------- /src/renderer/assets/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/icons/16x16.png -------------------------------------------------------------------------------- /src/renderer/assets/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/icons/24x24.png -------------------------------------------------------------------------------- /src/renderer/assets/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/icons/256x256.png -------------------------------------------------------------------------------- /src/renderer/assets/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/icons/32x32.png -------------------------------------------------------------------------------- /src/renderer/assets/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/icons/48x48.png -------------------------------------------------------------------------------- /src/renderer/assets/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/icons/512x512.png -------------------------------------------------------------------------------- /src/renderer/assets/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/icons/64x64.png -------------------------------------------------------------------------------- /src/renderer/assets/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/icons/96x96.png -------------------------------------------------------------------------------- /src/renderer/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/src/renderer/assets/logo.png -------------------------------------------------------------------------------- /src/renderer/components/AboutView.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/components/DisplayDate.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 34 | -------------------------------------------------------------------------------- /src/renderer/components/DisplayPath.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 28 | 29 | 41 | -------------------------------------------------------------------------------- /src/renderer/components/ErrorView.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 45 | 46 | -------------------------------------------------------------------------------- /src/renderer/components/FileLink.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 47 | 48 | 70 | -------------------------------------------------------------------------------- /src/renderer/components/PythonDetailView.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 144 | -------------------------------------------------------------------------------- /src/renderer/components/PythonInstallerView.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /src/renderer/components/PythonListView.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 41 | -------------------------------------------------------------------------------- /src/renderer/components/PythonPackagesView.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 40 | -------------------------------------------------------------------------------- /src/renderer/components/PythonRow.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 62 | 63 | 68 | -------------------------------------------------------------------------------- /src/renderer/components/PythonTable.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 37 | -------------------------------------------------------------------------------- /src/renderer/components/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 111 | 112 | -------------------------------------------------------------------------------- /src/renderer/components/WalkthroughView.vue: -------------------------------------------------------------------------------- 1 | 97 | 98 | 256 | 257 | 262 | -------------------------------------------------------------------------------- /src/renderer/components/installers/activepython/Description.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 29 | 30 | 32 | -------------------------------------------------------------------------------- /src/renderer/components/installers/activepython/Install.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /src/renderer/components/installers/activepython/Uninstall.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | 21 | 23 | -------------------------------------------------------------------------------- /src/renderer/components/installers/anaconda/Description.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 34 | 35 | 37 | -------------------------------------------------------------------------------- /src/renderer/components/installers/anaconda/Install.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 17 | -------------------------------------------------------------------------------- /src/renderer/components/installers/anaconda/Uninstall.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | 20 | 22 | -------------------------------------------------------------------------------- /src/renderer/components/installers/canopy/Description.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 27 | 28 | 30 | -------------------------------------------------------------------------------- /src/renderer/components/installers/canopy/Install.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /src/renderer/components/installers/canopy/Uninstall.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 22 | 23 | 25 | -------------------------------------------------------------------------------- /src/renderer/components/installers/homebrew/Description.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 32 | 33 | 35 | -------------------------------------------------------------------------------- /src/renderer/components/installers/homebrew/Install.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 18 | -------------------------------------------------------------------------------- /src/renderer/components/installers/homebrew/Uninstall.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 19 | 20 | 22 | -------------------------------------------------------------------------------- /src/renderer/components/installers/index.js: -------------------------------------------------------------------------------- 1 | import AnacondaDescription from './anaconda/Description' 2 | import AnacondaInstall from './anaconda/Install' 3 | import AnacondaUninstall from './anaconda/Uninstall' 4 | 5 | import HomebrewDescription from './homebrew/Description' 6 | import HomebrewInstall from './homebrew/Install' 7 | import HomebrewUninstall from './homebrew/Uninstall' 8 | 9 | import PyenvDescription from './pyenv/Description' 10 | import PyenvInstall from './pyenv/Install' 11 | import PyenvUninstall from './pyenv/Uninstall' 12 | 13 | import PipenvDescription from './pipenv/Description' 14 | import PipenvInstall from './pipenv/Install' 15 | import PipenvUninstall from './pipenv/Uninstall' 16 | 17 | import ActivePythonDescription from './activepython/Description' 18 | import ActivePythonInstall from './activepython/Install' 19 | import ActivePythonUninstall from './activepython/Uninstall' 20 | 21 | import CanopyDescription from './canopy/Description' 22 | import CanopyInstall from './canopy/Install' 23 | import CanopyUninstall from './canopy/Uninstall' 24 | 25 | import MinicondaDescription from './miniconda/Description' 26 | import MinicondaInstall from './miniconda/Install' 27 | import MinicondaUninstall from './miniconda/Uninstall' 28 | 29 | import PythonorgDescription from './pythonorg/Description' 30 | import PythonorgInstall from './pythonorg/Install' 31 | import PythonorgUninstall from './pythonorg/Uninstall' 32 | 33 | import WinpythonDescription from './winpython/Description' 34 | import WinpythonInstall from './winpython/Install' 35 | import WinpythonUninstall from './winpython/Uninstall' 36 | 37 | import PythonxyDescription from './xy/Description' 38 | import PythonxyInstall from './xy/Install' 39 | import PythonxyUninstall from './xy/Uninstall' 40 | 41 | export default { 42 | anaconda: { 43 | name: 'Anaconda', 44 | note: '', 45 | url: 'https://www.continuum.io/', 46 | Description: AnacondaDescription, 47 | Install: AnacondaInstall, 48 | Uninstall: AnacondaUninstall 49 | }, 50 | homebrew: { 51 | name: 'Homebrew', 52 | note: '', 53 | url: 'https://brew.sh/', 54 | Description: HomebrewDescription, 55 | Install: HomebrewInstall, 56 | Uninstall: HomebrewUninstall 57 | }, 58 | pyenv: { 59 | name: 'pyenv', 60 | note: '', 61 | url: 'https://github.com/pyenv/pyenv', 62 | Description: PyenvDescription, 63 | Install: PyenvInstall, 64 | Uninstall: PyenvUninstall 65 | }, 66 | pipenv: { 67 | name: 'pipenv', 68 | note: '', 69 | url: 'https://docs.pipenv.org/', 70 | Description: PipenvDescription, 71 | Install: PipenvInstall, 72 | Uninstall: PipenvUninstall 73 | }, 74 | activepython: { 75 | name: 'ActivePython', 76 | note: '', 77 | url: 'https://www.activestate.com/activepython/downloads/', 78 | Description: ActivePythonDescription, 79 | Install: ActivePythonInstall, 80 | Uninstall: ActivePythonUninstall 81 | }, 82 | canopy: { 83 | name: 'Enthought Canopy', 84 | note: '', 85 | url: 'https://enthought.com/product/canopy/', 86 | Description: CanopyDescription, 87 | Install: CanopyInstall, 88 | Uninstall: CanopyUninstall 89 | }, 90 | miniconda: { 91 | name: 'Miniconda', 92 | note: '', 93 | url: 'https://conda.io/miniconda.html', 94 | Description: MinicondaDescription, 95 | Install: MinicondaInstall, 96 | Uninstall: MinicondaUninstall 97 | }, 98 | pythonorg: { 99 | name: 'Python.org', 100 | note: '', 101 | url: 'https://www.python.org/', 102 | Description: PythonorgDescription, 103 | Install: PythonorgInstall, 104 | Uninstall: PythonorgUninstall 105 | }, 106 | winpython: { 107 | name: 'WinPython', 108 | note: '', 109 | url: 'https://winpython.github.io/', 110 | Description: WinpythonDescription, 111 | Install: WinpythonInstall, 112 | Uninstall: WinpythonUninstall 113 | }, 114 | xy: { 115 | name: 'Python(x,y)', 116 | note: '', 117 | url: 'https://python-xy.github.io/', 118 | Description: PythonxyDescription, 119 | Install: PythonxyInstall, 120 | Uninstall: PythonxyUninstall 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/renderer/components/installers/miniconda/Description.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 28 | 29 | 31 | -------------------------------------------------------------------------------- /src/renderer/components/installers/miniconda/Install.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 16 | -------------------------------------------------------------------------------- /src/renderer/components/installers/miniconda/Uninstall.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | 19 | 21 | -------------------------------------------------------------------------------- /src/renderer/components/installers/pipenv/Description.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /src/renderer/components/installers/pipenv/Install.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 23 | 24 | 26 | -------------------------------------------------------------------------------- /src/renderer/components/installers/pipenv/Uninstall.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 22 | -------------------------------------------------------------------------------- /src/renderer/components/installers/pyenv/Description.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /src/renderer/components/installers/pyenv/Install.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | 20 | 22 | -------------------------------------------------------------------------------- /src/renderer/components/installers/pyenv/Uninstall.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 18 | -------------------------------------------------------------------------------- /src/renderer/components/installers/pythonorg/Description.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 28 | 29 | 31 | -------------------------------------------------------------------------------- /src/renderer/components/installers/pythonorg/Install.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 18 | 19 | 21 | -------------------------------------------------------------------------------- /src/renderer/components/installers/pythonorg/Uninstall.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 22 | -------------------------------------------------------------------------------- /src/renderer/components/installers/winpython/Description.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /src/renderer/components/installers/winpython/Install.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /src/renderer/components/installers/winpython/Uninstall.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /src/renderer/components/installers/xy/Description.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 29 | 30 | 32 | -------------------------------------------------------------------------------- /src/renderer/components/installers/xy/Install.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /src/renderer/components/installers/xy/Uninstall.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /src/renderer/components/walkthrough/JupyterStep.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 47 | -------------------------------------------------------------------------------- /src/renderer/components/walkthrough/PipenvStep.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 27 | -------------------------------------------------------------------------------- /src/renderer/components/walkthrough/PyenvStep.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | -------------------------------------------------------------------------------- /src/renderer/components/walkthrough/PythonRemoval.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 109 | -------------------------------------------------------------------------------- /src/renderer/components/walkthrough/PythonThreeStep.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 26 | -------------------------------------------------------------------------------- /src/renderer/components/walkthrough/Step.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 36 | -------------------------------------------------------------------------------- /src/renderer/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import App from './App' 4 | import router from './router' 5 | import store from './store' 6 | 7 | import { faLock, faUpload, faDownload, faSync, faLink, faExternalLinkSquareAlt } from '@fortawesome/fontawesome-free-solid' 8 | import fontawesome from '@fortawesome/fontawesome' 9 | 10 | fontawesome.library.add(faLock) 11 | fontawesome.library.add(faUpload) 12 | fontawesome.library.add(faDownload) 13 | fontawesome.library.add(faSync) 14 | fontawesome.library.add(faLink) 15 | fontawesome.library.add(faExternalLinkSquareAlt) 16 | 17 | if (!process.env.IS_WEB) Vue.use(require('vue-electron')) 18 | Vue.config.productionTip = false 19 | 20 | /* eslint-disable no-new */ 21 | new Vue({ 22 | components: { App }, 23 | router, 24 | store, 25 | template: '' 26 | }).$mount('#app') 27 | -------------------------------------------------------------------------------- /src/renderer/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import AboutView from '@/components/AboutView' 4 | import PythonListView from '@/components/PythonListView' 5 | import PythonDetailView from '@/components/PythonDetailView' 6 | import WalkthroughView from '@/components/WalkthroughView' 7 | import ErrorView from '@/components/ErrorView' 8 | 9 | Vue.use(Router) 10 | 11 | export default new Router({ 12 | scrollBehavior (to, from, savedPosition) { 13 | return { x: 0, y: 0 } 14 | }, 15 | routes: [ 16 | { 17 | path: '/errors', 18 | name: 'errors', 19 | component: ErrorView 20 | }, 21 | { 22 | path: '/walkthrough', 23 | name: 'walkthrough', 24 | component: WalkthroughView 25 | }, 26 | { 27 | path: '/', 28 | name: 'main', 29 | component: PythonListView 30 | }, 31 | { 32 | path: '/pythons', 33 | name: 'pythons', 34 | component: PythonListView 35 | }, 36 | { 37 | path: '/python/:path', 38 | name: 'python-detail', 39 | component: PythonDetailView 40 | }, 41 | { 42 | path: '/about', 43 | name: 'about', 44 | component: AboutView 45 | } 46 | ] 47 | }) 48 | -------------------------------------------------------------------------------- /src/renderer/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import VueResource from 'vue-resource' 4 | import { ipcRenderer } from 'electron' 5 | import { EventEmitter } from 'events' 6 | 7 | EventEmitter.defaultMaxListeners = 30 8 | 9 | Vue.use(Vuex) 10 | Vue.use(VueResource) 11 | 12 | const asyncWatcher = store => { 13 | store.subscribe((mutation, state) => { 14 | let pieces = mutation.type.split('_') 15 | if (pieces[0] === 'FETCH') { 16 | store.commit('COMPLETED_DATA', pieces[1]) 17 | } 18 | }) 19 | 20 | store.subscribeAction((action, state) => { 21 | if (action.type.indexOf('fetch') === 0) { 22 | var type = action.type.replace('fetch', '').toUpperCase() 23 | store.commit('PENDING_DATA', type) 24 | } 25 | }) 26 | } 27 | 28 | export default new Vuex.Store({ 29 | strict: process.env.NODE_ENV !== 'production', 30 | plugins: [asyncWatcher], 31 | state: { 32 | locked: false, 33 | pythons: null, 34 | pyenv: null, 35 | pyenvs: null, 36 | pyenv_activated: null, 37 | pipenvs: null, 38 | jupyter: null, 39 | jupyters: null, 40 | pending: [] 41 | }, 42 | mutations: { 43 | FETCH_PYTHONS (state, pythons) { 44 | if (state.locked) { 45 | return 46 | } 47 | pythons.sort((a, b) => { 48 | if (b.default_commands.length === a.default_commands.length) { 49 | // return b.ctime - a.ctime 50 | if (!a.installer) { 51 | return 1 52 | } 53 | if (!b.installer) { 54 | return -1 55 | } 56 | return a.installer.localeCompare(b.installer) 57 | } else { 58 | return b.default_commands.length - a.default_commands.length 59 | } 60 | }) 61 | state.pythons = pythons 62 | }, 63 | FETCH_PACKAGES (state, data) { 64 | if (state.locked) { 65 | return 66 | } 67 | var match = state.pythons.find((python) => { 68 | return python.path === data.path 69 | }) 70 | match.packages = data.packages 71 | match.package_overcount = data.package_overcount 72 | }, 73 | FETCH_PYENVS (state, data) { 74 | if (state.locked) { 75 | return 76 | } 77 | state.pyenv_activated = data.activated 78 | state.pyenvs = data.pyenvs 79 | state.pyenv = data.pyenvs.find((pyenv) => { 80 | return pyenv.default_commands.indexOf('pyenv') !== -1 81 | }) 82 | }, 83 | FETCH_PIPENVS (state, data) { 84 | if (state.locked) { 85 | return 86 | } 87 | state.pipenvs = data.pipenvs 88 | state.pipenv = data.pipenvs.find((pipenv) => { 89 | return pipenv.default_commands.indexOf('pipenv') !== -1 || pipenv.default_commands.indexOf('pipenv.exe') !== -1 90 | }) 91 | }, 92 | FETCH_JUPYTERS (state, data) { 93 | if (state.locked) { 94 | return 95 | } 96 | state.jupyters = data.jupyters 97 | state.jupyter = data.jupyters.find((jupyter) => { 98 | return jupyter.default_commands.indexOf('jupyter') !== -1 || jupyter.default_commands.indexOf('jupyter.exe') !== -1 99 | }) 100 | }, 101 | FETCH_SYSTEM (state, data) { 102 | if (state.locked) { 103 | return 104 | } 105 | state.system = data.system 106 | }, 107 | PENDING_DATA (state, type) { 108 | state.pending.push(type) 109 | }, 110 | COMPLETED_DATA (state, type) { 111 | var copy = state.pending.slice() 112 | copy.splice(state.pending.indexOf(type), 1) 113 | state.pending = copy 114 | }, 115 | LOCK (state) { 116 | state.locked = true 117 | } 118 | }, 119 | actions: { 120 | lock ({ commit }, data) { 121 | commit('LOCK', data) 122 | }, 123 | pendingData ({ commit }, type) { 124 | commit('PENDING_DATA', type) 125 | }, 126 | completedData ({ commit }, type) { 127 | commit('COMPLETED_DATA', type) 128 | }, 129 | fetchPythons ({ commit, state }) { 130 | if (state.locked) { 131 | return Promise.resolve() 132 | } 133 | return new Promise((resolve, reject) => { 134 | ipcRenderer.once(`get-pythons`, (event, data) => { 135 | commit('FETCH_PYTHONS', data.pythons) 136 | resolve() 137 | }) 138 | ipcRenderer.send(`get-pythons`) 139 | }) 140 | }, 141 | fetchPyenvs ({ commit, state }) { 142 | if (state.locked) { 143 | return Promise.resolve() 144 | } 145 | return new Promise((resolve, reject) => { 146 | ipcRenderer.once(`get-pyenvs`, (event, data) => { 147 | commit('FETCH_PYENVS', data) 148 | resolve() 149 | }) 150 | ipcRenderer.send(`get-pyenvs`) 151 | }) 152 | }, 153 | fetchPipenvs ({ commit, state }) { 154 | if (state.locked) { 155 | return Promise.resolve() 156 | } 157 | return new Promise((resolve, reject) => { 158 | ipcRenderer.once(`get-pipenvs`, (event, data) => { 159 | commit('FETCH_PIPENVS', data) 160 | resolve() 161 | }) 162 | ipcRenderer.send(`get-pipenvs`) 163 | }) 164 | }, 165 | fetchJupyters ({ commit, state }) { 166 | if (state.locked) { 167 | return Promise.resolve() 168 | } 169 | return new Promise((resolve, reject) => { 170 | ipcRenderer.once(`get-jupyters`, (event, data) => { 171 | commit('FETCH_JUPYTERS', data) 172 | resolve() 173 | }) 174 | ipcRenderer.send(`get-jupyters`) 175 | }) 176 | }, 177 | fetchSystem ({ commit, state }) { 178 | if (state.locked) { 179 | return Promise.resolve() 180 | } 181 | return new Promise((resolve, reject) => { 182 | ipcRenderer.once(`get-system`, (event, data) => { 183 | commit('FETCH_SYSTEM', data) 184 | resolve() 185 | }) 186 | ipcRenderer.send(`get-system`) 187 | }) 188 | }, 189 | fetchPackages ({ commit, state }, pythonPath) { 190 | if (state.locked) { 191 | return Promise.resolve() 192 | } 193 | return new Promise((resolve, reject) => { 194 | /* The listener responds to every set-packages, so 195 | we need to make sure we're listening for the right one 196 | and then remove the listener & process if it's a match */ 197 | let responder = (event, data) => { 198 | if (pythonPath === data.path) { 199 | ipcRenderer.removeListener('get-packages', responder) 200 | commit('FETCH_PACKAGES', data) 201 | resolve() 202 | } 203 | } 204 | ipcRenderer.on(`get-packages`, responder) 205 | ipcRenderer.send(`get-packages`, pythonPath) 206 | }) 207 | } 208 | } 209 | }) 210 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/static/.gitkeep -------------------------------------------------------------------------------- /static/font-awesome/HELP-US-OUT.txt: -------------------------------------------------------------------------------- 1 | I hope you love Font Awesome. If you've found it useful, please do me a favor and check out my latest project, 2 | Fort Awesome (https://fortawesome.com). It makes it easy to put the perfect icons on your website. Choose from our awesome, 3 | comprehensive icon sets or copy and paste your own. 4 | 5 | Please. Check it out. 6 | 7 | -Dave Gandy 8 | -------------------------------------------------------------------------------- /static/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/static/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /static/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/static/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /static/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/static/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /static/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/static/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /static/font-awesome/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/static/font-awesome/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /static/font-awesome/less/animated.less: -------------------------------------------------------------------------------- 1 | // Animated Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .@{fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /static/font-awesome/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .@{fa-css-prefix}-pull-left { float: left; } 11 | .@{fa-css-prefix}-pull-right { float: right; } 12 | 13 | .@{fa-css-prefix} { 14 | &.@{fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.@{fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .@{fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /static/font-awesome/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /static/font-awesome/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /static/font-awesome/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "animated.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | @import "screen-reader.less"; 19 | -------------------------------------------------------------------------------- /static/font-awesome/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /static/font-awesome/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /static/font-awesome/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | .fa-icon-rotate(@degrees, @rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})"; 16 | -webkit-transform: rotate(@degrees); 17 | -ms-transform: rotate(@degrees); 18 | transform: rotate(@degrees); 19 | } 20 | 21 | .fa-icon-flip(@horiz, @vert, @rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)"; 23 | -webkit-transform: scale(@horiz, @vert); 24 | -ms-transform: scale(@horiz, @vert); 25 | transform: scale(@horiz, @vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | .sr-only() { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | .sr-only-focusable() { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /static/font-awesome/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), 9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /static/font-awesome/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .@{fa-css-prefix}-rotate-90, 15 | :root .@{fa-css-prefix}-rotate-180, 16 | :root .@{fa-css-prefix}-rotate-270, 17 | :root .@{fa-css-prefix}-flip-horizontal, 18 | :root .@{fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /static/font-awesome/less/screen-reader.less: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { .sr-only(); } 5 | .sr-only-focusable { .sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /static/font-awesome/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /static/font-awesome/scss/_animated.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .#{$fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /static/font-awesome/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .#{$fa-css-prefix}-pull-left { float: left; } 11 | .#{$fa-css-prefix}-pull-right { float: right; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .#{$fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /static/font-awesome/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /static/font-awesome/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /static/font-awesome/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /static/font-awesome/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /static/font-awesome/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | @mixin fa-icon-rotate($degrees, $rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})"; 16 | -webkit-transform: rotate($degrees); 17 | -ms-transform: rotate($degrees); 18 | transform: rotate($degrees); 19 | } 20 | 21 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)"; 23 | -webkit-transform: scale($horiz, $vert); 24 | -ms-transform: scale($horiz, $vert); 25 | transform: scale($horiz, $vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | @mixin sr-only { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | @mixin sr-only-focusable { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /static/font-awesome/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), 9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /static/font-awesome/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /static/font-awesome/scss/_screen-reader.scss: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { @include sr-only(); } 5 | .sr-only-focusable { @include sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /static/font-awesome/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /static/font-awesome/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "animated"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | @import "screen-reader"; 19 | -------------------------------------------------------------------------------- /static/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/static/icons/128x128.png -------------------------------------------------------------------------------- /static/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/static/icons/16x16.png -------------------------------------------------------------------------------- /static/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/static/icons/24x24.png -------------------------------------------------------------------------------- /static/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/static/icons/256x256.png -------------------------------------------------------------------------------- /static/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/static/icons/32x32.png -------------------------------------------------------------------------------- /static/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/static/icons/48x48.png -------------------------------------------------------------------------------- /static/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/static/icons/512x512.png -------------------------------------------------------------------------------- /static/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/static/icons/64x64.png -------------------------------------------------------------------------------- /static/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littlecolumns/python-wrangler/c6e910d29ada4093d3e6524645ac186ba1a4483d/static/icons/96x96.png -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "assert": true, 7 | "expect": true, 8 | "should": true, 9 | "__static": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/e2e/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Set BABEL_ENV to use proper env config 4 | process.env.BABEL_ENV = 'test' 5 | 6 | // Enable use of ES6+ on required files 7 | require('babel-register')({ 8 | ignore: /node_modules/ 9 | }) 10 | 11 | // Attach Chai APIs to global scope 12 | const { expect, should, assert } = require('chai') 13 | global.expect = expect 14 | global.should = should 15 | global.assert = assert 16 | 17 | // Require all JS files in `./specs` for Mocha to consume 18 | require('require-dir')('./specs') 19 | -------------------------------------------------------------------------------- /test/e2e/specs/Launch.spec.js: -------------------------------------------------------------------------------- 1 | import utils from '../utils' 2 | 3 | describe('Launch', function () { 4 | beforeEach(utils.beforeEach) 5 | afterEach(utils.afterEach) 6 | 7 | it('shows the proper application title', function () { 8 | return this.app.client.getTitle() 9 | .then(title => { 10 | expect(title).to.equal('electron') 11 | }) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /test/e2e/utils.js: -------------------------------------------------------------------------------- 1 | import electron from 'electron' 2 | import { Application } from 'spectron' 3 | 4 | export default { 5 | afterEach () { 6 | this.timeout(10000) 7 | 8 | if (this.app && this.app.isRunning()) { 9 | return this.app.stop() 10 | } 11 | }, 12 | beforeEach () { 13 | this.timeout(10000) 14 | this.app = new Application({ 15 | path: electron, 16 | args: ['dist/electron/main.js'], 17 | startTimeout: 10000, 18 | waitTimeout: 10000 19 | }) 20 | 21 | return this.app.start() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/unit/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | Vue.config.devtools = false 3 | Vue.config.productionTip = false 4 | 5 | // require all test files (files that ends with .spec.js) 6 | const testsContext = require.context('./specs', true, /\.spec$/) 7 | testsContext.keys().forEach(testsContext) 8 | 9 | // require all src files except main.js for coverage. 10 | // you can also change this to match only the subset of files that 11 | // you want coverage for. 12 | const srcContext = require.context('../../src/renderer', true, /^\.\/(?!main(\.js)?$)/) 13 | srcContext.keys().forEach(srcContext) 14 | -------------------------------------------------------------------------------- /test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const merge = require('webpack-merge') 5 | const webpack = require('webpack') 6 | 7 | const baseConfig = require('../../.electron-vue/webpack.renderer.config') 8 | const projectRoot = path.resolve(__dirname, '../../src/renderer') 9 | 10 | console.log('Project root is', projectRoot) 11 | 12 | // Set BABEL_ENV to use proper preset config 13 | process.env.BABEL_ENV = 'test' 14 | 15 | let webpackConfig = merge(baseConfig, { 16 | devtool: '#inline-source-map', 17 | plugins: [ 18 | new webpack.DefinePlugin({ 19 | 'process.env.NODE_ENV': '"testing"' 20 | }) 21 | ] 22 | }) 23 | 24 | // don't treat dependencies as externals 25 | delete webpackConfig.entry 26 | delete webpackConfig.externals 27 | delete webpackConfig.output.libraryTarget 28 | 29 | // apply vue option to apply isparta-loader on js 30 | webpackConfig.module.rules 31 | .find(rule => rule.use.loader === 'vue-loader').use.options.loaders.js = 'babel-loader' 32 | 33 | module.exports = config => { 34 | config.set({ 35 | browsers: ['visibleElectron'], 36 | client: { 37 | useIframe: false 38 | }, 39 | coverageReporter: { 40 | dir: './coverage', 41 | reporters: [ 42 | { type: 'lcov', subdir: '.' }, 43 | { type: 'text-summary' } 44 | ] 45 | }, 46 | customLaunchers: { 47 | 'visibleElectron': { 48 | base: 'Electron', 49 | flags: ['--show'] 50 | } 51 | }, 52 | frameworks: ['mocha', 'chai'], 53 | files: ['./index.js'], 54 | preprocessors: { 55 | './index.js': ['webpack', 'sourcemap'] 56 | }, 57 | reporters: ['spec', 'coverage'], 58 | singleRun: true, 59 | webpack: webpackConfig, 60 | webpackMiddleware: { 61 | noInfo: true 62 | } 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /test/unit/specs/LandingPage.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import LandingPage from '@/components/LandingPage' 3 | 4 | describe('LandingPage.vue', () => { 5 | it('should render correct contents', () => { 6 | const vm = new Vue({ 7 | el: document.createElement('div'), 8 | render: h => h(LandingPage) 9 | }).$mount() 10 | 11 | expect(vm.$el.querySelector('.title').textContent).to.contain('Welcome to your new project!') 12 | }) 13 | }) 14 | --------------------------------------------------------------------------------