├── .eslintignore
├── static
├── .gitkeep
├── icons
│ ├── 16x16.png
│ ├── 24x24.png
│ ├── 32x32.png
│ ├── 48x48.png
│ ├── 64x64.png
│ ├── 96x96.png
│ ├── 128x128.png
│ ├── 256x256.png
│ └── 512x512.png
└── font-awesome
│ ├── fonts
│ ├── FontAwesome.otf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.ttf
│ ├── fontawesome-webfont.woff
│ └── fontawesome-webfont.woff2
│ ├── less
│ ├── screen-reader.less
│ ├── fixed-width.less
│ ├── larger.less
│ ├── list.less
│ ├── core.less
│ ├── stacked.less
│ ├── font-awesome.less
│ ├── bordered-pulled.less
│ ├── rotated-flipped.less
│ ├── path.less
│ ├── animated.less
│ └── mixins.less
│ ├── scss
│ ├── _fixed-width.scss
│ ├── _screen-reader.scss
│ ├── _larger.scss
│ ├── _list.scss
│ ├── _core.scss
│ ├── font-awesome.scss
│ ├── _stacked.scss
│ ├── _bordered-pulled.scss
│ ├── _rotated-flipped.scss
│ ├── _path.scss
│ ├── _animated.scss
│ └── _mixins.scss
│ └── HELP-US-OUT.txt
├── src
├── renderer
│ ├── assets
│ │ ├── .gitkeep
│ │ ├── logo.png
│ │ ├── icons
│ │ │ ├── 16x16.png
│ │ │ ├── 24x24.png
│ │ │ ├── 32x32.png
│ │ │ ├── 48x48.png
│ │ │ ├── 64x64.png
│ │ │ ├── 96x96.png
│ │ │ ├── 128x128.png
│ │ │ ├── 256x256.png
│ │ │ └── 512x512.png
│ │ ├── fonts
│ │ │ ├── inconsolata-webfont.woff
│ │ │ └── inconsolata-webfont.woff2
│ │ ├── font-awesome
│ │ │ ├── fonts
│ │ │ │ ├── FontAwesome.otf
│ │ │ │ ├── fontawesome-webfont.eot
│ │ │ │ ├── fontawesome-webfont.ttf
│ │ │ │ ├── fontawesome-webfont.woff
│ │ │ │ └── fontawesome-webfont.woff2
│ │ │ ├── less
│ │ │ │ ├── fixed-width.less
│ │ │ │ ├── screen-reader.less
│ │ │ │ ├── larger.less
│ │ │ │ ├── list.less
│ │ │ │ ├── core.less
│ │ │ │ ├── stacked.less
│ │ │ │ ├── font-awesome.less
│ │ │ │ ├── bordered-pulled.less
│ │ │ │ ├── rotated-flipped.less
│ │ │ │ ├── path.less
│ │ │ │ ├── animated.less
│ │ │ │ └── mixins.less
│ │ │ ├── scss
│ │ │ │ ├── _fixed-width.scss
│ │ │ │ ├── _screen-reader.scss
│ │ │ │ ├── _larger.scss
│ │ │ │ ├── _list.scss
│ │ │ │ ├── _core.scss
│ │ │ │ ├── font-awesome.scss
│ │ │ │ ├── _stacked.scss
│ │ │ │ ├── _bordered-pulled.scss
│ │ │ │ ├── _rotated-flipped.scss
│ │ │ │ ├── _path.scss
│ │ │ │ ├── _animated.scss
│ │ │ │ └── _mixins.scss
│ │ │ └── HELP-US-OUT.txt
│ │ └── fonts.css
│ ├── components
│ │ ├── installers
│ │ │ ├── xy
│ │ │ │ ├── Install.vue
│ │ │ │ ├── Uninstall.vue
│ │ │ │ └── Description.vue
│ │ │ ├── canopy
│ │ │ │ ├── Install.vue
│ │ │ │ ├── Uninstall.vue
│ │ │ │ └── Description.vue
│ │ │ ├── winpython
│ │ │ │ ├── Uninstall.vue
│ │ │ │ ├── Install.vue
│ │ │ │ └── Description.vue
│ │ │ ├── activepython
│ │ │ │ ├── Install.vue
│ │ │ │ ├── Uninstall.vue
│ │ │ │ └── Description.vue
│ │ │ ├── miniconda
│ │ │ │ ├── Install.vue
│ │ │ │ ├── Uninstall.vue
│ │ │ │ └── Description.vue
│ │ │ ├── pythonorg
│ │ │ │ ├── Uninstall.vue
│ │ │ │ ├── Install.vue
│ │ │ │ └── Description.vue
│ │ │ ├── homebrew
│ │ │ │ ├── Uninstall.vue
│ │ │ │ ├── Install.vue
│ │ │ │ └── Description.vue
│ │ │ ├── pyenv
│ │ │ │ ├── Uninstall.vue
│ │ │ │ ├── Install.vue
│ │ │ │ └── Description.vue
│ │ │ ├── anaconda
│ │ │ │ ├── Uninstall.vue
│ │ │ │ ├── Install.vue
│ │ │ │ └── Description.vue
│ │ │ ├── pipenv
│ │ │ │ ├── Install.vue
│ │ │ │ ├── Uninstall.vue
│ │ │ │ └── Description.vue
│ │ │ └── index.js
│ │ ├── PythonInstallerView.vue
│ │ ├── PythonTable.vue
│ │ ├── DisplayDate.vue
│ │ ├── walkthrough
│ │ │ ├── PipenvStep.vue
│ │ │ ├── PythonThreeStep.vue
│ │ │ ├── Step.vue
│ │ │ ├── JupyterStep.vue
│ │ │ ├── PythonRemoval.vue
│ │ │ └── PyenvStep.vue
│ │ ├── DisplayPath.vue
│ │ ├── PythonListView.vue
│ │ ├── PythonPackagesView.vue
│ │ ├── AboutView.vue
│ │ ├── ErrorView.vue
│ │ ├── FileLink.vue
│ │ ├── PythonRow.vue
│ │ ├── Sidebar.vue
│ │ ├── PythonDetailView.vue
│ │ └── WalkthroughView.vue
│ ├── main.js
│ ├── router
│ │ └── index.js
│ ├── store
│ │ └── index.js
│ └── App.vue
├── main
│ ├── executable
│ │ ├── pipenv.js
│ │ ├── index.js
│ │ ├── jupyter.js
│ │ ├── pyenv.js
│ │ ├── python.js
│ │ └── executable.js
│ ├── index.dev.js
│ ├── utils
│ │ ├── refresh-path.js
│ │ └── case-converter.js
│ ├── worker.js
│ └── index.js
└── index.ejs
├── icons
├── icon.icns
└── icon.ico
├── .gitignore
├── test
├── .eslintrc
├── e2e
│ ├── specs
│ │ └── Launch.spec.js
│ ├── index.js
│ └── utils.js
└── unit
│ ├── specs
│ └── LandingPage.spec.js
│ ├── index.js
│ └── karma.conf.js
├── .babelrc
├── appveyor.yml
├── .eslintrc.js
├── README.md
├── .travis.yml
├── LICENSE
├── .electron-vue
├── dev-client.js
├── webpack.main.config.js
├── webpack.web.config.js
├── build.js
├── dev-runner.js
└── webpack.renderer.config.js
└── package.json
/.eslintignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/renderer/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/icons/icon.icns
--------------------------------------------------------------------------------
/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/icons/icon.ico
--------------------------------------------------------------------------------
/static/icons/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/static/icons/16x16.png
--------------------------------------------------------------------------------
/static/icons/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/static/icons/24x24.png
--------------------------------------------------------------------------------
/static/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/static/icons/32x32.png
--------------------------------------------------------------------------------
/static/icons/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/static/icons/48x48.png
--------------------------------------------------------------------------------
/static/icons/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/static/icons/64x64.png
--------------------------------------------------------------------------------
/static/icons/96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/static/icons/96x96.png
--------------------------------------------------------------------------------
/static/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/static/icons/128x128.png
--------------------------------------------------------------------------------
/static/icons/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/static/icons/256x256.png
--------------------------------------------------------------------------------
/static/icons/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/static/icons/512x512.png
--------------------------------------------------------------------------------
/src/renderer/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/src/renderer/assets/logo.png
--------------------------------------------------------------------------------
/src/renderer/assets/icons/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/src/renderer/assets/icons/16x16.png
--------------------------------------------------------------------------------
/src/renderer/assets/icons/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/src/renderer/assets/icons/24x24.png
--------------------------------------------------------------------------------
/src/renderer/assets/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/src/renderer/assets/icons/32x32.png
--------------------------------------------------------------------------------
/src/renderer/assets/icons/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/src/renderer/assets/icons/48x48.png
--------------------------------------------------------------------------------
/src/renderer/assets/icons/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/src/renderer/assets/icons/64x64.png
--------------------------------------------------------------------------------
/src/renderer/assets/icons/96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/src/renderer/assets/icons/96x96.png
--------------------------------------------------------------------------------
/src/renderer/assets/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/src/renderer/assets/icons/128x128.png
--------------------------------------------------------------------------------
/src/renderer/assets/icons/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/src/renderer/assets/icons/256x256.png
--------------------------------------------------------------------------------
/src/renderer/assets/icons/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/src/renderer/assets/icons/512x512.png
--------------------------------------------------------------------------------
/static/font-awesome/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/static/font-awesome/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/src/renderer/assets/fonts/inconsolata-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/src/renderer/assets/fonts/inconsolata-webfont.woff
--------------------------------------------------------------------------------
/static/font-awesome/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/static/font-awesome/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/static/font-awesome/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/static/font-awesome/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/static/font-awesome/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/static/font-awesome/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/src/main/executable/pipenv.js:
--------------------------------------------------------------------------------
1 | import Executable from './executable'
2 |
3 | class PipenvExecutable extends Executable {
4 | }
5 |
6 | export default PipenvExecutable
7 |
--------------------------------------------------------------------------------
/src/renderer/assets/fonts/inconsolata-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/src/renderer/assets/fonts/inconsolata-webfont.woff2
--------------------------------------------------------------------------------
/static/font-awesome/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/static/font-awesome/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/src/renderer/assets/font-awesome/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/src/renderer/assets/font-awesome/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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/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/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/littlecolumns/python-wrangler/master/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/master/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/master/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/master/src/renderer/assets/font-awesome/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/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/screen-reader.less:
--------------------------------------------------------------------------------
1 | // Screen Readers
2 | // -------------------------
3 |
4 | .sr-only { .sr-only(); }
5 | .sr-only-focusable { .sr-only-focusable(); }
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/components/installers/xy/Install.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
12 |
13 |
15 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/canopy/Install.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
12 |
13 |
15 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/xy/Uninstall.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
You can remove Python(x,y) using Add/Remove Programs in the Control Panel.
4 |
5 |
6 |
7 |
12 |
13 |
15 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/winpython/Uninstall.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
You can remove WinPython using Add/Remove Programs in the Control Panel.
4 |
5 |
6 |
7 |
12 |
13 |
15 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/components/installers/winpython/Install.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Visit the WinPython page and click one of the "Downloads" links near the top - whatever has the highest version number (probably 3.9) and the right bit-ness (probably 64-bit).
4 |
5 |
6 |
7 |
12 |
13 |
15 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/components/installers/activepython/Install.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
12 |
13 |
15 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/components/PythonInstallerView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Installer Details: {{ installer.name }}
4 |
This Python was installed using {{ installer.name }}.
5 |
6 |
7 |
8 |
9 |
Removal instructions
10 |
11 |
12 |
13 |
14 |
19 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/renderer/components/installers/miniconda/Install.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Visit Continuum's Miniconda website to download the Miniconda installer. You want the 64-bit version for Python 3 (unless your computer is 32-bit, you really really need Python 2).
4 |
IMPORTANT: During installation, there's sometimes be a checkbox that says "Do you want to add Anaconda to the PATH?" If it shows up, be sure to check it.
5 |
6 |
7 |
8 |
13 |
14 |
16 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/pythonorg/Uninstall.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
On Windows, you can remove Python.org's installation using Add/Remove Programs in the Control Panel.
4 |
On OS X, you can remove this version of Python by deleting its folder.
5 |
This Python lives in , so you just need to remove that folder.
6 |
7 |
8 |
9 |
19 |
20 |
22 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/homebrew/Uninstall.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
When we uninstall, we're only removing the Python installed by Homebrew, not all of Homebrew. The process is pretty simple - Homebrew comes with a brew uninstall command that's used to remove software it installed.
4 |
This particular Python can be removed using
5 |
6 | brew uninstall pythonpython@2
7 |
8 |
9 |
10 |
11 |
12 |
13 |
19 |
20 |
22 |
--------------------------------------------------------------------------------
/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/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/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/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/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/components/installers/miniconda/Uninstall.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
When we uninstall a Python installed by Miniconda, we need to also uninstall all of the additional Miniconda stuff that came with it. To do that, we can either use Add/Remove Programs (Windows only), or remove the directory Miniconda lives in (Windows/OS X).
4 |
This specific Python lives in , so you just need to remove that folder.
5 |
6 |
7 |
8 |
18 |
19 |
21 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/pyenv/Uninstall.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
When we uninstall, we're only removing the Python installed by pyenv, not pyenv itself. The process is pretty simple - Homebrew comes with a pyenv uninstall command that's used to remove software it installed.
4 |
This particular Python can be removed using pyenv uninstall {{ python.version }}
5 |
6 |
If you did want to remove pyenv itself and all of the Python versions it installed, you can remove the ~/.pyenv folder.
7 |
8 |
9 |
10 |
15 |
16 |
18 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/anaconda/Uninstall.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
When we uninstall a Python installed by Anaconda, we need to also uninstall all of the additional Anaconda stuff that came with it. To do that, we can either use Add/Remove Programs (Windows only), or remove the directory Anaconda lives in (Windows/OS X).
4 |
This specific Python lives in , so you just need to remove that folder.
5 |
6 |
7 |
8 |
19 |
20 |
22 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/components/installers/canopy/Uninstall.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
On Windows, remove Canopy through Add/Remove Programs.
4 |
On OS X, follow the folowing steps:
5 |
6 | - Open up and delete
Canopy.app and Enthought Canopy (64-bit)
7 | - Open up and delete
Enthought and Canopy
8 |
9 |
10 |
11 |
12 |
22 |
23 |
25 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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/renderer/components/PythonTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | | Command |
6 | Version |
7 | Installation |
8 | Packages |
9 | Location(s) |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
37 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/pipenv/Install.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | On Windows, first install any version of Python, and then use pip to install pipenv:
5 | pip install pipenv
6 |
7 |
8 | On OS X, you'll need to first install Homebrew. Once that's done you can run brew install pipenv.
9 |
10 |
You can find more details on using pipenv in the documentation.
11 |
12 |
13 |
14 |
23 |
24 |
26 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/components/installers/pipenv/Uninstall.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
If you'd like to remove a pipenv Python permanently, you'll need to remove the folder it's located in.
4 |
This particular Python lives in , so if you delete that folder this Python will disappear.
5 |
If you remove a Python that a virtual environment is using somewhere, you're sure to get an error about it next time you try to use that virtual enviroment. But don't worry, you can always install that Python again — pipenv is pretty nice about those things.
6 |
7 |
8 |
9 |
19 |
20 |
22 |
--------------------------------------------------------------------------------
/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/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/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/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/components/installers/anaconda/Install.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Visit the Continuum website to download the Anaconda installer. You want the 64-bit version for Python 3 (unless your computer is 32-bit, you really really need Python 2).
4 |
If the installer asks if you want to install Anaconda just for you or for all users, pick just for you. This will make installing extra software easier in the future.
5 |
IMPORTANT: During installation, there's sometimes be a checkbox that says "Do you want to add Anaconda to the PATH?" If it shows up, be sure to check it. If you forget, though, we'll try to remind you!
6 |
7 |
8 |
9 |
14 |
15 |
17 |
--------------------------------------------------------------------------------
/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/renderer/components/DisplayDate.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ displayDate }}
4 |
5 |
6 |
7 |
34 |
--------------------------------------------------------------------------------
/src/renderer/components/walkthrough/PipenvStep.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Looking for pipenv...
5 |
6 |
7 |
8 |
9 | Great! pipenv was successfully installed. You can use it when a project needs to be isolated from everything else.
10 | If you'd like to learn more about using pipenv, read this.
11 |
12 |
13 |
14 |
15 |
16 |
17 |
27 |
--------------------------------------------------------------------------------
/src/renderer/components/walkthrough/PythonThreeStep.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Great! You're all set with Python 3 from Python.org.
5 |
6 |
7 | You have Python 3 installed, and maybe it's from Python.org. We can't tell because it looks like a nice generic Python. You're probably okay!
8 |
9 |
10 | We're starting off with a nice plain Python installation that we're going to layer everything on top of.
11 |
12 |
13 |
14 |
15 |
16 |
26 |
--------------------------------------------------------------------------------
/src/renderer/components/DisplayPath.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ path }}
4 | in PATH
5 | symlink
6 | file missing
7 |
8 |
9 |
10 |
28 |
29 |
41 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/homebrew/Install.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
To install Homebrew, you'll need to use the Terminal, a text-only way of operating your computer that looks like a movie about hacking. To open Terminal, search for Terminal from Spotlight, or go to Applications > Utilities > Terminal.
4 |
Then, visit the Homebrew website for installation instructions. It's just a little bit of cut-and-paste into Terminal and will only take a minute.
5 |
Once Homebrew is installed, run the command brew install readline xz to install a couple pieces of software that Python might want later.
6 |
Then you can install Homebrew's Python: run the command brew install python3 to install Python 3. If you really need it, you can also install Python 2 with brew install python2
7 |
8 |
9 |
10 |
15 |
16 |
18 |
--------------------------------------------------------------------------------
/src/renderer/components/walkthrough/Step.vue:
--------------------------------------------------------------------------------
1 |
2 |
23 |
24 |
25 |
36 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/renderer/components/PythonListView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Python Installations
4 |
5 |
6 | We found {{ pythons.length }} Pythons installed on your system. There might be more if they're exceptionally polite about hiding when not in use.
7 |
8 |
9 | Scanning your system for Python installations...
10 |
11 |
12 |
13 |
14 |
15 |
16 |
41 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/activepython/Uninstall.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
ActivePython includes uninstall instructions in their documentation, broken up by version number:
4 | 2.6,
5 | 2.7,
6 | 3.5,
7 | 3.6,
8 | 3.7,
9 | 3.8.
10 |
Your version is {{ python.version }}. You'll want to select from the left-hand menu - pick OS X or Windows or whichever platform you're on - then scroll down to the uninstall instructions down at the bottom of the page.
11 |
If you're using Windows, you should be able to use Add/Remove Programs.
12 |
13 |
14 |
15 |
20 |
21 |
23 |
--------------------------------------------------------------------------------
/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/components/installers/canopy/Description.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Canopy is Python + a ton of science-y/data-y tools, all packaged together. It's practically the same thing as Anaconda but not as popular.
4 |
5 |
6 |
UPSIDES
7 |
8 | - If you work in data analysis or science (like astrophysics science, not computer science), Canopy has all of the complicated-to-install pieces already there. It's also easy to launch Python, no goofing around on the command line required.
9 |
10 |
11 |
DOWNSIDES
12 |
13 | - If you're looking for a big ol' scientific package shebang, it isn't as popular as Anaconda. When you hit a problem, you're going to want to google for it, and you'll find more answers for more popular software.
14 |
15 |
16 |
RECOMMENDATION
17 |
Skip it. If you want something similar, go with Anaconda.
18 |
19 |
20 |
21 |
22 |
27 |
28 |
30 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/pyenv/Install.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
To install pyenv on OS X, first you'll have install Homebrew, which helps you manage software from the command line. Follow the instructions here.
4 |
If you get a prompt that says Password: [] and you type and type and nothing shows up... just type your password and hit enter! The command line is just hiding your typing from anyone who might be looking at your screen.
5 |
Once Homebrew is installed, you can install pyenv by running...
6 |
brew install pyenv
7 |
Once Pyenv is installed, you can install a new Python and set it as the default by running a few more lines:
8 |
pyenv install 3.10.3
9 | pyenv global 3.10.3
10 |
That installs Python 3.10.3 to your computer, then sets it as the default. If you're curious, you can use pyenv install --list to see all the versions you can install.
11 |
12 |
13 |
14 |
19 |
20 |
22 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/pyenv/Description.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Pyenv is a simple Python version manager. It allows you to install multiple versions of Python on your machine, from the command line.
4 |
5 |
UPSIDES
6 |
7 | - Allows you to switch between multiple version of Python easily.
8 | - Is very easy to use.
9 | - Is simple to uninstall.
10 |
11 |
12 |
DOWNSIDES
13 |
14 | - It's easily confused with
pipenv, which is a different project.
15 | - Traditional virtualenv support.
16 | - Not as good as
pipenv for managing individual virtual environments.
17 |
18 |
19 |
RECOMMENDATION
20 |
I think using pyenv is an excellent choice to manage your global Python version. Combine it with pipenv for individual projects and you'll be good to go!
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/xy/Description.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Python(x,y) is a "free scientific and engineering development software for numerical computations, data analysis and data visualization based on Python programming language, Qt graphical user interfaces and Spyder interactive scientific development environment." It isn't current or that common, so I don't know much about it.
4 |
5 |
6 |
UPSIDES
7 |
10 |
11 |
DOWNSIDES
12 |
13 | - It hasn't been updated in forever
14 | - Only available for Python 2, not Python 3
15 | - Windows only
16 |
17 |
18 |
RECOMMENDATION
19 |
While Python(x,y) might have been a valid choice a hundred years ago, these days the competition for "Python plus add-ons" is too fierce for it to be a sensible option. If you want something in the same boat but modern, use Anaconda.
20 |
21 |
22 |
23 |
24 |
29 |
30 |
32 |
--------------------------------------------------------------------------------
/src/renderer/components/PythonPackagesView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Packages
4 | overcount due to no pip installation
5 |
6 |
This Python has {{ python.packages.length }} packages available. To install more, run the following:
7 |
{{ scriptPath }} -m pip install <packagename>
8 |
9 |
10 |
11 |
12 | | Name |
13 | Version |
14 |
15 |
16 |
17 |
18 | | {{ pkg.name }} |
19 | {{ pkg.version }} |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
40 |
--------------------------------------------------------------------------------
/src/renderer/components/AboutView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
About Python Wrangler
4 |
5 |
Python Wrangler was made by Jonathan Soma under the guise of Little Columns, because installing Python is remarkably un-fun.
6 |
More details on the Little Columns site, or peruse the code in the GitHub repo.
7 |
Having trouble?
8 |
Feel free to reach out at hello@littlecolumns.com or @dangerscarf. To help me debug your situation, click the Export button on the sidebar and include that text file as an attachment.
9 |
Acknowledgements
10 |
Super sweet snake icon made by Freepik from www.flaticon.com, and is licensed by CC 3.0 BY. And to all my poor, poor, students who were strong-armed into beta testing this.
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/pythonorg/Install.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Visit the Python.org downloads page and install the version for your system. You'll want Python 3 unless George Washington has specifically requested you time travel back to 1775 to work on some Python 2 code with him.
4 |
The site is probably asking you to install 3.10.x. Sometimes the newest version is a little too advanced and has some issues running all the software we'll need, so it's usually a good idea to go back one version.
5 |
For example: if it's recommending 3.10.4, then we might want 3.10.3 instead. It can be downloaded here).
6 |
7 |
During installation there will be a checkbox that says something like "Add Python to environment variables" or "Do you want to add Python to the PATH?"
8 |
By default, it is unchecked. When you see this option, click the checkbox next to it.
9 |
If you missed it the first time, just run the installer again and you'll have another chance.
10 |
11 |
12 |
13 |
14 |
18 |
19 |
21 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/renderer/components/ErrorView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Errors
4 |
5 |
6 |
We ran into errors when trying to analyze your system! These might or might not matter.
7 |
They aren't supposed to make sense to you, really.
8 |
9 |
10 |
You're error-free!
11 |
12 |
13 |
14 |
15 |
{{ executable.path }}
16 |
17 |
{{ error.message }}
18 |
{{ error.stack }}
19 |
20 |
21 |
22 |
23 |
24 |
45 |
46 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/pythonorg/Description.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Python.org distributes the official Python source code - every other version of Python out there is just a repackaged version of this one.
4 |
5 |
6 |
UPSIDES
7 |
8 | - You'll be using a standard kind of Python that doesn't do many weird things when being installed. You also won't pollute your computer with a bunch of commands and programs tied to specific companies.
9 |
10 |
11 |
DOWNSIDES
12 |
13 | - It's more difficult to keep Python up to date - you'll need to re-download the installer and re-run it each time a new Python comes out, while also (optionally) removing your old versions.
14 | - Python is installed as a super magic all-powerful user, instead of a nice comfortable installation just for you. You might wind up typing your password in a lot to confirm changes.
15 |
16 |
17 |
RECOMMENDATION
18 |
It's a fine, simple basis for building up a Python installation that isn't full of other junk
19 |
20 |
21 |
22 |
23 |
28 |
29 |
31 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/pipenv/Description.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Pipenv is the officially recommended packaging tool from Python.org. It's great at juggling Python versions and package installs, but just for single projects.
4 |
5 |
6 |
UPSIDES
7 |
8 | - Officially sanctions by the Powers that Be.
9 | - Super easy to use compared to previous virtual environment tools
10 | - Simple to change versions and keep track of the packages you need for a project
11 |
12 |
13 |
DOWNSIDES
14 |
15 | - Does NOT manage your global Python version (a.k.a. your default
python).
16 | - Not the same as pyenv, which has a similar name.
17 |
18 |
19 |
RECOMMENDATION
20 |
I am over the moon for pipenv, it's fantastic for managing virtual environments. If you're a web developer or plan on distributing packages or working with others on a project, I highly highly highly recommend it. I'd use something else for installing the default Python on your computer, though.
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/winpython/Description.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
WinPython is one of the many scientific distributions of Python, but it's neat because it's isolated, which means it doesn't touch anything on your computer other than the directory you install it in. In theory you could install a thousand versions of WinPython on your machine without any of them knowing about the others.
4 |
5 |
6 |
UPSIDES
7 |
8 | - Includes all of the science-y and data-y things you could ever want
9 | - Isolated/portable so it doesn't affect anything else on your computer
10 | - You can create your own bundles of packages/versions for distribution
11 |
12 |
13 |
DOWNSIDES
14 |
15 | - Not a standard Python-y way of doing things
16 | - Windows only
17 | - Not as popular as Anaconda, so probably fewer answers when googling
18 |
19 |
20 |
RECOMMENDATION
21 |
WinPython looks pretty nice, I'm going to be honest. If you want something similar that's more popular and (AFAIK) more fully-featured, though, use Anaconda.
22 |
23 |
24 |
25 |
26 |
31 |
32 |
34 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/activepython/Description.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
ActivePython is Python + a few other tools, packaged up by a company called ActiveState. I'm not that familiar with it, but it looks like it's mainly used by businesses who are really scared of risk and gives them someone to call when their Python breaks.
4 |
5 |
6 |
UPSIDES
7 |
8 | - ActivePython does some really friendly Python management when you're installing it so you can uninstall it easily later.
9 | - If you're a business who really needs enterprise support, I guess it's a good solution?
10 |
11 |
12 |
DOWNSIDES
13 |
14 | - It adds its own package manager to the mix, called
PyPM. Python already has one called pip and you don't want to use some weird thing no one else uses.
15 | - I don't know much about it, but it doesn't seem to have anything special about it except the whole enterprise thing.
16 |
17 |
18 |
RECOMMENDATION
19 |
Ignore it.
20 |
21 |
22 |
23 |
24 |
29 |
30 |
32 |
--------------------------------------------------------------------------------
/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/renderer/components/installers/miniconda/Description.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Anaconda is Python + a ton of science-y/data-y tools, all packaged together. It's huge, though! Miniconda, as you might guess, is the smaller version of Anaconda. It installs Python and the magic command conda that lets you install the additional Anaconda tools as you need them.
4 |
5 |
6 |
UPSIDES
7 |
8 | - Anaconda is great because it takes all of this sometimes-hard-to-set-up software and it just works. Miniconda (potentially) has all the magic of Anaconda with a much smaller download size.
9 |
10 |
11 |
DOWNSIDES
12 |
13 | - Being encouraged to use
conda in addition to more traditional Python tools like pip can get confusing, especially if you don't need all of the science-y things that Anaconda is useful for.
14 | - If you need a million different science things, you might as well get Anaconda. If you don't need those tools, you probably don't really need
conda.
15 |
16 |
17 |
RECOMMENDATION
18 |
Skip it unless you'd like Anaconda but are concerned about disk space.
19 |
20 |
21 |
22 |
23 |
28 |
29 |
31 |
--------------------------------------------------------------------------------
/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/renderer/components/FileLink.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ path || folder }}
5 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
47 |
48 |
70 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/components/PythonRow.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ command }}
6 |
7 | |
8 |
9 |
10 |
11 |
12 | Executable missing!
13 | See details
14 |
15 | {{ python.version }}
16 |
17 | |
18 |
19 | {{ installer }}
20 |
21 | |
22 |
23 |
24 | {{ python.packages.length }} packages
25 | overcount
28 |
29 |
30 | Searching...
31 |
32 | |
33 |
34 |
35 |
36 | |
37 |
38 |
39 |
40 |
62 |
63 |
68 |
--------------------------------------------------------------------------------
/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/renderer/components/walkthrough/JupyterStep.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Great! You can use the {{ jupyterKernelInstalled.spec.display_name }} kernel when starting up a new notebook.
5 |
If you'd like to learn more about how we set that up (or troubleshoot in the future) check out this video for more details.
6 |
7 |
8 | We're going to install Jupyter and allow it to use our default Python.
9 | Paste each line below into the command line, hitting enter after each. Read below if you want to know what each line does.
10 |
11 | eval "$(pyenv init --path)"
12 | python -m pip install notebook
13 | python -m ipykernel install --user
14 | These commands do the following:
15 |
16 | - Activates pyenv
17 | - Installs jupyter
18 | - Adds a new Python 3 to Jupyter, which hooks into our pyenv Python (and overwrites any old Python 3 so you don't get confused)
19 |
20 |
21 |
22 | python -m pip install notebook
23 | python -m ipykernel install --user
24 | These commands do the following:
25 |
26 | - Installs jupyter
27 | - Adds a new Python 3 to Jupyter, which hooks into our pyenv Python (and overwrites any old Python 3 so you don't get confused)
28 |
29 |
30 |
31 |
32 |
33 |
34 |
47 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/renderer/components/installers/homebrew/Description.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Homebrew is a "package manager" for OS X - a simple tool that helps you easily install command-line (and non-command-line) software.
4 |
5 |
UPSIDES
6 |
7 | - It's much easier to keep Python up to date than when installing with other methods.
8 | - Homebrew is the best way to install a lot of technical software on a Mac, so using it to install Python is good practice.
9 | - Only standard Python will be installed, so you won't clutter your system with a lot of extra stuff you don't need. On top of that, if you use Homebrew to install both Python 2 and Python 3, they won't conflict with one another.
10 | - Homebrew does all of its installing in its own private secret little area, and only puts symlinks (shortcuts) into the more public areas of your computer. That does a lot to help keep things organized and avoid conflicts.
11 |
12 |
13 |
DOWNSIDES
14 |
15 | - Homebrew is only for OS X. You also need to use the Terminal to use Homebrew. If you find a text-only interface overwhelming, you can avoid it by using Anaconda.
16 | - Unlike Anaconda, Homebrew won't automatically install more science-y and data-y components. If you need them, you're probably going to spend a while installing those extra bits and pieces.
17 | - Because Homebrew does interact with more public parts of your system (e.g.
/usr/local/bin), there's always the chance that something could get messed up. It has a nice tool called brew doctor that helps you troubleshoot, though.
18 |
19 |
20 |
RECOMMENDATION
21 |
If you're using OS X, I strongly recommend you install Python using Homebrew. If you work with lots of science or people who use Anaconda, though, use Anaconda.
22 |
If you're using Windows or Linux, you can't use Homebrew.
23 |
24 |
25 |
26 |
27 |
32 |
33 |
35 |
--------------------------------------------------------------------------------
/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/components/installers/anaconda/Description.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Anaconda is Python + a ton of science-y/data-y tools, all packaged together. Installing those tools individually can be a real pain, so Anaconda is a quick way to get a fully-featured Python installation.
4 |
5 |
6 |
UPSIDES
7 |
8 | - If you work in data analysis or science (like astrophysics science, not computer science), Anaconda has all of the complicated-to-install pieces already there. Scientists love using Anaconda, so everyone will know exactly what you're talking about when you mention it.
9 | - You also don't need to spend any time on the command line (which many would actually call a downside, I suppose!).
10 |
11 |
12 |
DOWNSIDES
13 |
14 | - If you don't work in academia/science, most people don't use Anaconda, so you might end up a little bit stranded. Also, when you're googling for answers you'll come across a lot of people talking about using
pip or virtualenv to manage Python-y things on your computer, and you'll say what are those?
15 |
16 | - Y'see, Anaconda puts a new tool on top of Python - called
conda - which is kind of a replacement for the standard installing-Python-things tool pip and the keeping-things-organized tool virtualenv. It was written back when pip was terrible, but modern-day pip is fine for most everyone. While conda is great in some specific situations, juggling the two can be confusing.
17 |
18 |
19 |
RECOMMENDATION
20 |
21 | - If you work in science or data analysis, go for Anaconda, it's convenient and what (most) everyone uses.
22 | - If you're coding for the web, don't use Anaconda. It isn't for that kind of stuff.
23 | - If you're just starting out and don't know what the heck you're doing, I recommend you keep things simple and skip Anaconda. You can always install it later.
24 |
25 |
26 |
27 |
28 |
29 |
34 |
35 |
37 |
--------------------------------------------------------------------------------
/src/renderer/components/walkthrough/PythonRemoval.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | You're all set! You don't have any Pythons to remove.
5 |
6 |
7 | We found {{ unwantedPythons.length }} unneeded Python installations. We recommend removing anything you don't need — let's make a list. (but if you'd rather not remove them, it's probably also okay!)
8 |
9 |
10 |
11 |
12 | | Version |
13 | Installer |
14 | Path |
15 | Instructions |
16 |
17 |
18 |
19 | |
20 | unknown
21 | {{ python.installer }}
22 | |
23 | {{ python.version }} |
24 | |
25 |
26 |
31 | |
32 |
33 |
34 |
35 |
36 |
37 | + Show Pythons we're keeping
38 | - Hide list
39 |
40 |
41 |
42 |
We're going to keep everything installed by pyenv, pipenv, Python.org, or Homebrew. We aren't going to use the Homebrew ones, but software installed with brew might depend on them. Some software depends on the Python.org version, so we'll keep that, too. We're also keeping anything with a mystery origin, just to be safe.
43 |
44 |
45 | | Version |
46 | Installer |
47 | Path |
48 |
49 |
50 |
51 | | {{ python.version }} |
52 |
53 | unknown
54 | {{ python.installer }}
55 | |
56 | |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
109 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/Sidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
47 |
48 |
49 |
111 |
112 |
--------------------------------------------------------------------------------
/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/renderer/components/walkthrough/PyenvStep.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Great! pyenv was successfully installed. Currently {{ defaultPyenvVersion }} is your default Python.
5 |
6 | We need to tell pyenv to install Python 3. We'll use 3.8.10 here, but you can use any other version if you have a preference.
7 | pyenv install 3.10.3
8 | pyenv global 3.10.3
9 |
10 |
11 |
12 | + What if I want a different default version?
13 | - Hide instructions
14 |
15 |
16 | If you'd like to install a different default Python, you'd run the code below. Just replace 3.10.3 with the version you're interested in. pyenv install --list will show you all possible versions.
17 | pyenv install 3.10.3
18 | pyenv global 3.10.3
19 | Note: If you change your default Python this you'll need to reinstall all your packages.
20 |
21 | You can find more about pyenv here, or just read the command reference.
22 |
23 |
24 |
25 | + Show troubleshooting tips
26 | - Hide troubleshooting
27 |
28 |
29 |
30 | -
31 |
If you're having trouble on OS X 10.15 Catalina, it might be because you need to install a tool that Apple used to include. Visit this page and download/install Command Line Tools for XCode (there will be a few of them, just pick the first!).
32 |
33 | -
34 |
If you're having trouble on OS X 10.14 Mojave, it's because we need to manually install some things first. To fix this, run the following comamnd:
35 | sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target /
36 | brew reinstall zlib
37 |
38 | -
39 |
If you're using OS X 10.13 or earlier, you might be able to fix it with this command:
40 | xcode-select --install
41 | brew reinstall zlib
42 |
43 |
44 |
45 |
46 |
47 |
48 | pyenv is installed, but not activated! This means you won't get your cool new Pythons automatically. If you just installed/uninstalled something, try to click Refresh to see if this error goes away.
49 |
50 |
To set it up to activate, paste the following code into the command line.
51 | echo 'export PYENV_ROOT="$HOME/.pyenv"' >> {{ shellStartupFile }}
52 | echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> {{ shellStartupFile }}
53 | echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init --path)"\nfi' >> {{ shellStartupFile }}
54 | Once you do that, your computer will use pyenv Pythons instead of the other ones on your system.
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/renderer/components/PythonDetailView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Loading Python details...
5 |
Python {{ python.version }} details
6 |
7 |
8 |
This is everything we know about this particular Python.
9 |
10 |
11 |
12 | | Key |
13 | Value |
14 |
15 |
16 |
17 |
18 | | Commands |
19 |
20 |
21 | None available, you can't run this from the command line without typing its full path
22 |
23 |
24 | {{ command }}
25 |
26 | |
27 |
28 | | Version |
29 | {{ python.version }}
30 | Python 3
31 | Python 2
32 | |
33 | | Path | {{ python.path }} |
34 |
35 | | Installed by |
36 | {{ python.installer }} |
37 |
38 | | Installed at | |
39 | | Raw version output | {{ python.raw_version }} |
40 |
41 | | All paths |
42 |
43 |
44 |
45 | |
46 |
47 |
48 | | syspath |
49 |
50 |
51 | {{ path }}
52 |
53 | |
54 |
55 |
56 | | Jupyter |
57 |
58 |
59 | Available in Jupyter as {{ kernelSpec.display_name }}
60 |
61 |
62 | Jupyter does not know about this python. To install it with the name {{ python.raw_version }}, run:
63 | {{ python.path }} -m ipykernel install --user --name "{{ python.raw_version }}"
64 | You can also pick a more descriptive name, something like "data projects" or whatever.
65 |
66 |
67 | Asking for Jupyter info...
68 |
69 | |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
144 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/renderer/components/WalkthroughView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Python Setup Walkthrough
4 |
5 |
Python Wrangler is very opinionated about how you should set up your machine. When you're done, your setup will look like this:
6 |
7 | - Python 3 from python.org, since some software assumes it's installed
8 | - Pipenv for modern virtual environment management
9 | - Jupyer Notebook, pointing to the Python installed by pyenv
10 | - OS X only Pyenv to switch between different (global) Python versions, like 38 vs 2.7
11 |
12 |
If you'd like more details about the software: python.org, pyenv, pipenv, Jupyter Notebook
13 |
14 |
15 |
18 |
19 |
If the steps below ask you to type something or enter a command, they want you to do it on the command line.
20 |
21 | On Windows, open up the Start Menu and search for cmd or Command Prompt. Run this and you're all set.
22 |
23 | On a Mac, click the magnifying glass in the upper right-hand corner of your screen. Type Terminal in the box that comes up and hit enter.
24 |
25 | The text-y thing that comes up the command line - type your commands there, pressing enter after each one.
26 |
27 |
28 |
29 |
30 |
36 |
37 |
38 |
39 |
45 |
46 |
47 |
48 |
54 |
61 |
62 |
63 |
You're using Windows, so you don't need pyenv! You use the Python Launcher, which comes with Python 3.
64 |
65 |
66 |
67 |
73 |
74 |
75 |
76 |
82 |
83 |
84 |
85 |
88 |
89 |
Since you're on Windows, if you're planning on doing anything science-y, maps-y, or data-y, you probably want to install Visual Studio Build Tools. This allows you compile Python packages on your machine.
90 |
Now that you're done, check out Python Wrangler notes for tips on how to use your new system. It includes tips on virtual environments, switching Python versions, and anything else.
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
256 |
257 |
262 |
--------------------------------------------------------------------------------
/src/renderer/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
25 |
26 |
212 |
--------------------------------------------------------------------------------