├── vendor ├── launch.cmd └── terminal.py ├── images ├── command_compile.png ├── command_execute.png ├── command_redirect.png └── command_runinwindow.png ├── keymaps └── atom-shell-commands.cson ├── lib ├── sounds.js ├── config.js ├── terminal.js └── atom-shell-commands.js ├── menus └── atom-shell-commands.json ├── LICENSE.md ├── styles └── atom-shell-commands.less ├── package.json ├── CHANGELOG.md └── README.md /vendor/launch.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | %* 3 | pause 4 | 5 | -------------------------------------------------------------------------------- /images/command_compile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywind3000/atom-shell-commands/HEAD/images/command_compile.png -------------------------------------------------------------------------------- /images/command_execute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywind3000/atom-shell-commands/HEAD/images/command_execute.png -------------------------------------------------------------------------------- /images/command_redirect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywind3000/atom-shell-commands/HEAD/images/command_redirect.png -------------------------------------------------------------------------------- /images/command_runinwindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywind3000/atom-shell-commands/HEAD/images/command_runinwindow.png -------------------------------------------------------------------------------- /keymaps/atom-shell-commands.cson: -------------------------------------------------------------------------------- 1 | # Keybindings require three things to be fully defined: A selector that is 2 | # matched against the focused element, the keystroke and the command to 3 | # execute. 4 | # 5 | # Below is a basic keybinding which registers on all platforms by applying to 6 | # the root workspace element. 7 | 8 | # For more detailed documentation see 9 | # https://atom.io/docs/latest/behind-atom-keymaps-in-depth 10 | -------------------------------------------------------------------------------- /lib/sounds.js: -------------------------------------------------------------------------------- 1 | ( function( module ) { 2 | var bank = {}; 3 | module.exports.play = function(filename) { 4 | var path = require('path'); 5 | var filepath = path.resolve(filename); 6 | var audio = null; 7 | if (filepath in bank) { 8 | audio = bank[filepath]; 9 | } else { 10 | audio = new Audio(filepath); 11 | bank[filepath] = audio; 12 | } 13 | audio.play(); 14 | return true; 15 | } 16 | } ) (module); 17 | 18 | -------------------------------------------------------------------------------- /menus/atom-shell-commands.json: -------------------------------------------------------------------------------- 1 | { 2 | "menu" : [ 3 | { 4 | "label": "Packages", 5 | "submenu": [ 6 | { 7 | "label": "Atom Shell Commands", 8 | "submenu": [ 9 | { "label": "Stop Command", "command": "atom-shell-commands-config:stop" }, 10 | { "label": "Go To First Error", "command": "atom-shell-commands-config:error-first" }, 11 | { "label": "Go To Last Error", "command": "atom-shell-commands-config:error-last" }, 12 | { "label": "Go To Next Error", "command": "atom-shell-commands-config:error-next" }, 13 | { "label": "Go To Prev Error", "command": "atom-shell-commands-config:error-prev" }, 14 | { "type": "separator" } 15 | ] 16 | } 17 | ] 18 | } 19 | ] 20 | } 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /styles/atom-shell-commands.less: -------------------------------------------------------------------------------- 1 | // The ui-variables file is provided by base themes provided by Atom. 2 | // 3 | // See https://github.com/atom/atom-dark-ui/blob/master/styles/ui-variables.less 4 | // for a full listing of what's available. 5 | @import "ui-variables"; 6 | 7 | .atom-shell-commands-console { 8 | font-family: 'consolas', 'monospace'; 9 | padding: 10px; 10 | margin: 2px 6px 2px 0; 11 | border: 1px solid @pane-item-border-color; 12 | white-space: pre-line; 13 | background-color: @inset-panel-background-color; 14 | max-height: 24em; 15 | min-height: 5em; 16 | overflow-y: scroll; 17 | } 18 | 19 | .hide { 20 | display: none; 21 | } 22 | 23 | .stdout { 24 | color: @text-color; 25 | white-space: pre; 26 | } 27 | 28 | .stdout-match { 29 | color: @text-color-warning; 30 | white-space: pre; 31 | } 32 | 33 | .stdout-match:hover { 34 | text-decoration: underline; 35 | } 36 | 37 | .stderr { 38 | color: @text-color; 39 | white-space: pre; 40 | } 41 | 42 | .stderr-match { 43 | color: @text-color-warning; 44 | white-space: pre; 45 | } 46 | 47 | .stderr-match:hover { 48 | text-decoration: underline; 49 | } 50 | 51 | .selected { 52 | background-color: @background-color-highlight; 53 | } 54 | 55 | .echo { 56 | color: @text-color-subtle; 57 | white-space: pre; 58 | } 59 | 60 | .error { 61 | color: @text-color-error; 62 | white-space: pre; 63 | } 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atom-shell-commands", 3 | "version": "1.5.0", 4 | "description": "Customize shell commands. Similar to 'Run Commands' in NotePad++, 'User Tool' in EditPlus/UltraEdit, 'External Tool' in GEdit and 'Shell Commands' in TextMate.", 5 | "main": "./lib/atom-shell-commands", 6 | "keywords": [ 7 | "build", 8 | "compile", 9 | "make", 10 | "user", 11 | "tool", 12 | "shell", 13 | "command", 14 | "project" 15 | ], 16 | "homepage": "https://github.com/skywind3000/atom-shell-commands", 17 | "dependencies": { 18 | "space-pen": "^4", 19 | "xregexp": "^2.0.0", 20 | "atom-message-panel": "^1.2.4" 21 | }, 22 | "author": { 23 | "name": "skywind3000" 24 | }, 25 | "repository": "https://github.com/skywind3000/atom-shell-commands", 26 | "license": "MIT", 27 | "engines": { 28 | "atom": ">=1.0.0 <2.0.0" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/skywind3000/atom-shell-commands/issues" 32 | }, 33 | "_atomModuleCache": { 34 | "version": 1, 35 | "dependencies": [ 36 | { 37 | "name": "win-spawn", 38 | "version": "2.0.0", 39 | "path": "node_modules/win-spawn/index.js" 40 | } 41 | ], 42 | "extensions": { 43 | ".js": [ 44 | "lib/atom-shell-commands.js", 45 | "lib/config.js", 46 | "node_modules/win-spawn/index.js" 47 | ], 48 | ".json": [ 49 | "node_modules/win-spawn/package.json", 50 | "package.json" 51 | ] 52 | }, 53 | "folders": [ 54 | { 55 | "paths": [ 56 | "lib", 57 | "menus", 58 | "" 59 | ], 60 | "dependencies": { 61 | "win-spawn": "^2.0.0" 62 | } 63 | } 64 | ] 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | ( function( module ) { 2 | module.exports = { 3 | commands: { 4 | type: 'array', 5 | default: [], 6 | items: { 7 | type: 'object', 8 | properties: { 9 | name: { 10 | type: 'string', 11 | title: 'Name of the command' 12 | }, 13 | selector: { 14 | type: 'string', 15 | title: 'Atom selector', 16 | default: 'atom-workspace' 17 | }, 18 | command: { 19 | type: 'string', 20 | title: 'Command to be executer' 21 | }, 22 | arguments: { 23 | type: 'array', 24 | title: 'Extra arguments for the command', 25 | default: [], 26 | items: { 27 | type: 'string', 28 | } 29 | }, 30 | options: { 31 | type: 'object', 32 | title: 'Command modifier options, like run dir (cwd)', 33 | properties: { 34 | cwd: { 35 | type: 'string', 36 | default: '{FileDir}' 37 | }, 38 | env: { 39 | type: 'object', 40 | default: {} 41 | }, 42 | save: { 43 | type: 'boolean', 44 | default: false 45 | }, 46 | silent: { 47 | type: 'boolean', 48 | default: false 49 | }, 50 | keymap: { 51 | type: 'string', 52 | default: '' 53 | }, 54 | mode: { 55 | type: 'string', 56 | default: '' 57 | }, 58 | sound: { 59 | type: 'string', 60 | default: '' 61 | } 62 | }, 63 | default : {} 64 | }, 65 | matchs: { 66 | type: 'array', 67 | title: 'XRegExp expression for file, line, col', 68 | default: [], 69 | items: { 70 | type: 'string' 71 | } 72 | } 73 | }, 74 | title: 'Command configuration' 75 | }, 76 | title: 'Array of commands', 77 | description: 'Objects have the following properties: name, selector, command, arguments, options' 78 | } 79 | } 80 | } )( module ); 81 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.5.0 - 2016/10/7 2 | * Fixed: options missing 'mode' issue (a new feature which is developing in progress) 3 | 4 | ## 1.4.0 - 2016/5/17 5 | * New: `sound` option to play a sound file (wav/mp3/ogg) after command finished 6 | 7 | ## 1.3.15 - 2016/4/18 8 | * New: `silent` option to hide output panel by [@damncabbage](https://github.com/damncabbage). 9 | * Fixed: toggle command issue, by [@damncabbage](https://github.com/damncabbage) again. 10 | 11 | ## 1.3.14 - 2016/4/5 12 | * Fixed: catch spawn error on a no exist command 13 | 14 | ## 1.3.13 - 2016/4/3 15 | * Fixed: spawn failed in windows when command name contains space (eg. 'C:/program files (x86)/xxx') 16 | * Remove 'win-spawn' module which is really out-of-date and use 'child_process.spawn' directly 17 | 18 | ## 1.3.10 - 2016/4/1 19 | * Adjust the style of bottom output panel, make it more neat now. 20 | 21 | ## 1.3.0 - 2016/3/30 22 | * New '{CurWord}' macro to get the current word under cursor which can be passed to external manual or dictionary. 23 | * New quickfix mode in bottom output panel with direct command and hotkey to jump from one error to another without touching your mouse. 24 | 25 | ## 1.2.0 - 2016/3/17 26 | * Reduce loading time from 140ms to 2ms by using delay initializing and other optimizes. 27 | * Update document. 28 | 29 | ## 1.1.19 - 2016/3/13 30 | * Fixed exception when save=true in a untitled file. 31 | * Fixed {ProjectDir} becomes "null" when a file isn't in any project. 32 | * Update document. 33 | 34 | ## 1.1.11 - 2016/3/9 35 | * New 'stop' command in menu to stop child processes 36 | 37 | ## 1.1.10 - New Relase 38 | * New white-space:pre in the style of the bottom panel to avoid space issue. 39 | * New document about error match. 40 | * Simplify some module. 41 | 42 | ## 1.1.0 - Child Process Management 43 | * Child processes can be stop now. 44 | * Add child process return value prompt. 45 | * Fixed empty parameter item issue in config file. 46 | 47 | ## 1.0.0 - More Stable 48 | * Add keymap option in the config file. 49 | * Add {CurRow} {CurCol} {CurSelected} {CurTextLine} macros. 50 | * Rewrote some module. 51 | * Fixed numerous issues in some corners. 52 | 53 | ## 0.8.0 - More Powerful 54 | * Add atom-message-panel to replace old
panel. Bottom panel can resize now. 55 | * Add save option to indicate if you need save the file before execution. 56 | * Add XRegExp to match error output. 57 | * Add {ProjectDir} {ProjectRel} macros. 58 | * Update package description. 59 | * Fixed issues when editing a noname file (new file). 60 | * Implement caches to split new lines from stdout/stdin. 61 | * Fixed issues in windows. 62 | 63 | 64 | ## 0.5.0 - More Useful 65 | * Support {FileName} {FileExt} {FileNameNoExt} {FilePath} in macros. 66 | * Support options.env to setup system environment. 67 | * Display how long has the command been executed. 68 | * Fixed string replace issue in empty strings. 69 | 70 | ## 0.1.0 - First Release 71 | * Rewrite atom-user-commands and fixed from atom-user-commands. 72 | * Make first release 73 | -------------------------------------------------------------------------------- /lib/terminal.js: -------------------------------------------------------------------------------- 1 | ( function( module ) { 2 | var os = require('os'); 3 | var path = require('path'); 4 | var spawn = require('child_process').spawn; 5 | var dirname = __dirname; 6 | var vendor = path.resolve(dirname, '../vendor'); 7 | var osname = os.type(); 8 | var win32 = os.type() == 'Windows_NT'; 9 | var cmd = 'cmd.exe'; 10 | var gnome_terminal = undefined; 11 | var xterm = undefined; 12 | 13 | function search_bin(name, locations) { 14 | var process = require('process'); 15 | var path = require('path'); 16 | var fs = require('fs'); 17 | var os = require('os'); 18 | var win32 = os.type() == 'Windows_NT'; 19 | if (process.env.PATH) { 20 | var PATH = process.env.PATH; 21 | if (win32) { 22 | PATH = PATH.split(';'); 23 | } else { 24 | PATH = PATH.split(':'); 25 | } 26 | for (var i = 0; i < PATH.length; i++) { 27 | var filepath = path.join(PATH[i], name); 28 | try { 29 | fs.accessSync(filepath, fs.F_OK); 30 | return win32? filepath.split('/').join('\\') : filepath; 31 | } 32 | catch (e) { 33 | } 34 | } 35 | } 36 | if (locations) { 37 | for (var i = 0; i < locations.length; i++) { 38 | var filepath = path.join(locations[i], name); 39 | try { 40 | fs.accessSync(filepath, fs.F_OK); 41 | return win32? filepath.split('/').join('\\') : filepath; 42 | } 43 | catch (e) { 44 | } 45 | } 46 | } 47 | return undefined; 48 | } 49 | 50 | if (win32) { 51 | var loc = ['C:/Windows/System32', 'C:/Windows/SysWOW64']; 52 | loc.push('C:/WinNT/System32'); 53 | loc.push('C:/WinNT/SysWOW64'); 54 | cmd = search_bin('cmd.exe', loc); 55 | cmd = cmd.split('/').join('\\'); 56 | } 57 | else if (osname == 'Linux') { 58 | gnome_terminal = search_bin('gnome-terminal', ['/bin', '/usr/bin']); 59 | xterm = search_bin('xterm', ['/bin', '/usr/bin']); 60 | } 61 | 62 | module.exports.open_windows = function(command, argv, options) { 63 | var argument = ['/C', 'start', cmd, '/C']; 64 | argument.push(path.join(vendor, "launch.cmd")); 65 | argument.push(command); 66 | argument = argument.concat(argv); 67 | options.detached = true; 68 | options.stdout = 'ignore'; 69 | options.stderr = 'ignore'; 70 | options.shell = false; 71 | command = cmd; 72 | if (false) { 73 | command = 'cmd.exe'; 74 | argument = ['/C', 'start', cmd, '/C', 'echo', 'fuck']; 75 | } 76 | var proc = spawn(command, argument, options); 77 | } 78 | 79 | module.exports.open_linux_gnome = function(command, argv, options) { 80 | } 81 | 82 | module.exports.open_linux_xterm = function(command, argv, options) { 83 | } 84 | 85 | module.exports.open_darwin_terminal = function(command, argv, options) { 86 | } 87 | 88 | module.exports.open_terminal = function(command, argv, options) { 89 | options.detached = true; 90 | options.stdout = 'ignore'; 91 | options.stderr = 'ignore'; 92 | options.shell = false; 93 | if (osname == 'Windows_NT') { 94 | this.open_windows(command, argv, options); 95 | } 96 | else if (osname == 'Linux') { 97 | var terminal = 'xterm'; 98 | 99 | } 100 | } 101 | 102 | module.exports.test = function(a, b) { 103 | return a + b; 104 | } 105 | } ) (module); 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # atom-shell-commands package 2 | 3 | Customize shell commands for atom. Similar to 'Run Commands' in NotePad++, 'User Tool' in EditPlus/UltraEdit, 'External Tool' in GEdit and 'Shell Commands' in TextMate. 4 | 5 | Preface 6 | ------- 7 | This package enables you to setup shell commands as atom commands. Shell output will be captured and display in the atom's bottom panel.If you create a command named 'gcc-build', you will find 'atom-shell-commands:gcc-build' in the atom command palette, and then use keymap to setup a shotcut for it. 8 | 9 | I was a window user and switched to Atom from EditPlus/Notepad++. Previously I wrote a script in EditPlus to compile-run my C/C++ programs and execute Makefiles with customizable compile flags and run options. Atom has numerous community packages and I have tried some. Most of them are really great in certain extent but still cannot fully satisfy my need. Therefore I decided to reference their packages and make my own. 10 | 11 | Feature 12 | ------- 13 | 14 | - Customize shell command to invoke compiler or other tools with active document/project. 15 | - Customize arguments, working directory and system environment of the command. 16 | - Automatic create atom command for each shell command. 17 | - Keymap config allow you to make shortcut to each command. 18 | - Shell output (stdout/stderr) can be captured in the bottom panel. 19 | - Click the filename in the output panel will open it. 20 | - Regular expression to match filename and line number in the error output. 21 | - Hotkeys to navigate errors one by one just like quickfix in vim. 22 | - Fast and lightweight, loading time is less than 2 milliseconds (TimeCop). 23 | 24 | Installation 25 | ------------ 26 | apm install atom-shell-commands 27 | 28 | Setup 29 | ----- 30 | 31 | Configure commands on your config file (File->Open Your Config, or ~/.atom/config.cson) like this, add 'atom-shell-commands' in your user config file: 32 | ```cson 33 | "atom-shell-commands": 34 | commands: [ 35 | { 36 | name: "compile" 37 | command: "d:/dev/mingw/bin/gcc" 38 | arguments: [ 39 | "{FileName}" 40 | "-o" 41 | "{FileNameNoExt}.exe" 42 | ] 43 | options: 44 | cwd: "{FileDir}" 45 | keymap: 'ctrl-2' 46 | } 47 | ] 48 | ``` 49 | This will create the atom command "atom-shell-commands:compile" that you can now launch from the command palette or use the binding keymap 'ctrl-2'. It also generates an entry in the Atom Shell Commands menu under packages. A certain command is represented by: 50 | 51 | | Field | Mode | Description | 52 | |-------|----|---------| 53 | | `name` | **[required]** | The name of the target. Viewed in the menu | 54 | | `command` | **[required]** | The executable command | 55 | | `auguments` | *[optional]* | An array of arguments for the command | 56 | | `selector` | *[optional]* | atom selector, default is 'atom-workspace' | 57 | | `matchs` | *[optional]* | regular expression to match file name in output | 58 | | `options` | *[optional]* | additional options to config dir, environment, keymap etc | 59 | 60 | The `command`, `arguments` and `options` values accepts the variables below: 61 | 62 | | Macros | Description | 63 | |------------------|-------------| 64 | | {FilePath} | File name of current document with full path. | 65 | | {FileName} | File name of current document without path. | 66 | | {FileDir} | Full path of current document without the file name | 67 | | {FileExt} | File extension of current document | 68 | | {FileNameNoExt} | File name of current document without path and extension | 69 | | {ProjectDir} | Current project directory | 70 | | {ProjectRel} | File name relativize to current project directory | 71 | | {CurRow} | Current row(line number) where the cursor is located | 72 | | {CurCol} | Current column index where the cursor is located | 73 | | {CurSelected} | Selected text | 74 | | {CurLineText} | Current line text under cursor | 75 | | {CurWord} | Current word under cursor | 76 | 77 | You can setup as many commands as you wish to build with your project makefile, or compile a single source file directly, or compile your latex, or run grep in current directory, passing current word under cursor to external man help / dictionary / other external scripts, or just call svn diff with current file and redirect the output to the bottom panel. 78 | 79 | 80 | The `options` field is an key/value object contains: 81 | 82 | | Options | Mode | Description | 83 | |---------|------|-------| 84 | | cwd | *[optional]* | Working directory of the command | 85 | | save | *[optional]* | True or false(default) to save the current file before execute | 86 | | silent | *[optional]* | True or false(default); true will not show the message panel (if it's closed) when this command is run. | 87 | | keymap | *[optional]* | A keymap string as defined by Atom. Pressing this key combination will trigger the target. Examples: ctrl-alt-k or cmd-U. | 88 | | env | *[optional]* | Key/value based system environment setting | 89 | | sound | *[optional]* | File path for a wav/mp3/ogg file which will be played to remind you that the job is finished | 90 | 91 | Examples 92 | -------- 93 | 94 | Compiling with command 'gcc' 95 | 96 | ![](https://github.com/skywind3000/atom-shell-commands/blob/master/images/command_compile.png?raw=true) 97 | 98 | Running with command 'execute' 99 | 100 | ![](https://github.com/skywind3000/atom-shell-commands/blob/master/images/command_execute.png?raw=true) 101 | 102 | command 'execute' config: 103 | ```cson 104 | { 105 | name: "execute" 106 | command: "{FileNameNoExt}" 107 | options: 108 | cwd: "{FileDir}" 109 | } 110 | ``` 111 | 112 | Running in a new window 113 | 114 | ![](https://github.com/skywind3000/atom-shell-commands/blob/master/images/command_runinwindow.png?raw=true) 115 | 116 | command 'runinwindow' config: 117 | ```cson 118 | { 119 | name: "runinwindow" 120 | command: "cmd" 121 | arguments: [ 122 | "/C" 123 | "start" 124 | "d:/software/atom/launch.cmd" 125 | "{FileNameNoExt}" 126 | ] 127 | options: 128 | cwd: "{FileDir}" 129 | } 130 | ``` 131 | 132 | you need to write a batch file in windows to open a new window, here is source of launch.cmd: 133 | ``` 134 | @echo off 135 | %1 136 | pause 137 | exit 138 | ``` 139 | 140 | Lookup word in manual 141 | 142 | Current word under cursor can be passed to the external executables or scripts, you can use it to look up current word in dictionary or manual with a single hotkey stroke: 143 | 144 | ```cson 145 | { 146 | name: "man" 147 | command: "/usr/bin/man" 148 | arguments: [ 149 | "{CurWord}" 150 | ] 151 | options: 152 | cwd: "{FileDir}" 153 | keymap: "ctrl-alt-m" 154 | } 155 | ``` 156 | 157 | Error Matching 158 | -------------- 159 | 160 | Error matching lets you specify a single regular expression or a list of regular expressions, which capture the output of your build command and open the correct file, row and column of the error. For instance: 161 | 162 | ``` 163 | ztest_hello.cpp: In function 'int main()': 164 | ztest_hello.cpp:7:10: error: expected initializer before 'int' 165 | ``` 166 | 167 | Would be matched with the expression: `^(?[\\/0-9a-zA-Z\\._\\\\:]+):(?\\d+):(?\\d+):`. After the build has failed, click on the error output line, the file would be opened and the cursor would be placed at row 7, column 10. 168 | 169 | Note the syntax for match groups. This is from the [XRegExp](http://xregexp.com/) package 170 | and has the syntax for named groups: `(? RE )` where `name` would be the name of the group 171 | matched by the regular expression `RE`. 172 | 173 | The following named groups can be matched from the output: 174 | * `file` - **[required]** the file to open. May be relative `cwd` or absolute. `(? RE)`. 175 | * `line` - *[optional]* the line the error resides on. `(? RE)`. 176 | * `col` - *[optional]* the column the error resides on. `(? RE)`. 177 | 178 | Since the regular expressions are written in the user config file as .cson format, backslashes must be escaped. 179 | 180 | The `file` should be an absolute path, or relative the `cwd` specified. If no `cwd` has been specified, default value '/' will be used. 181 | 182 | Example user config file which is using error matching: 183 | 184 | ```cson 185 | *: 186 | "atom-shell-commands": 187 | commands: [ 188 | { 189 | name: "compile" 190 | command: "d:/dev/mingw/bin/gcc" 191 | arguments: [ 192 | "{FileName}" 193 | "-o" 194 | "{FileNameNoExt}.exe" 195 | ] 196 | matchs: [ 197 | "^(?[\\/0-9a-zA-Z\\._\\\\:]+):(?\\d+):(?\\d+):" 198 | "^(?[\\/0-9a-zA-Z\\._\\\\:]+):(?\\d+):" 199 | "^(?[\\/0-9a-zA-Z\\._\\\\:]+)\\s*\\((?\\d+)\\)\\s*:*:" 200 | ] 201 | options: 202 | cwd: "{FileDir}" 203 | keymap: 'ctrl-2' 204 | save: true 205 | } 206 | ] 207 | ``` 208 | 209 | This will match the `file`, `line` and `col` in both clang/gcc or msvc error output. 210 | 211 | Quickfix 212 | -------- 213 | 214 | Atom-shell-commands has a special design in the output panel to speedup the edit-compile-edit cycle. This is inspired by the quickfix mode in vim. The idea is to save the error messages from the compiler and use builtin commands to jump to the errors one by one. You can examine each problem and fix it, without having to remember all the error messages. 215 | 216 | | Command | Description | 217 | |---------|-------| 218 | | atom-shell-commands-config:error-first | go to the first error | 219 | | atom-shell-commands-config:error-last | go to the last error | 220 | | atom-shell-commands-config:error-next | go to the next error | 221 | | atom-shell-commands-config:error-prev | go to the previous error | 222 | 223 | To avoid hotkey conflict to other packages, Atom-shell-commands has none predefined keymap, just leave the it to user. You can trigger them from `Atom Shell Commands` menu under `Packages`, or from command palette directly. 224 | 225 | The most efficient way is binding to your keymap config by simply adding few lines in ~/.atom/keymap.cson (or open it in the `File` menu): 226 | ```cson 227 | 'atom-workspace': 228 | 'F9' : 'atom-shell-commands-config:error-next' 229 | 'F10': 'atom-shell-commands-config:error-prev' 230 | ``` 231 | 232 | Now you can have your F9/F10 to navigate errors without leaving your hand from keyboard to mouse/touch pad. 233 | 234 | Misc 235 | ---- 236 | atom-shell-commands has been tested in windows, mac os and ubuntu. You can use 'open' in mac os or '/usr/bin/gnome-terminal' in ubuntu to open a new window and execute your command. 237 | 238 | As executing program in a new terminal window correctly is a little tricky thing, I create a script to let you open a new terminal window to execute your program in both Windows, Linux (ubuntu), Cygwin and Mac OS X, you can try it from: https://github.com/skywind3000/terminal. 239 | 240 | TO-DO 241 | ----- 242 | - Commands could be setup in a new GUI window, not only in user config. 243 | 244 | 245 | 246 | Reference 247 | --------- 248 | 249 | - [https://github.com/DPISA/atom-user-commands](https://github.com/DPISA/atom-user-commands "atom-user-commands") (based on it). 250 | - [https://github.com/joefitzgerald/go-plus](https://github.com/joefitzgerald/go-plus) (referenced). 251 | - [https://github.com/noseglid/atom-build](https://github.com/noseglid/atom-build) (referenced). 252 | - [https://github.com/ksxatompackages/cmd-exec](https://github.com/ksxatompackages/cmd-exec) (referenced). 253 | 254 | 255 | 256 | -------------------------------------------------------------------------------- /lib/atom-shell-commands.js: -------------------------------------------------------------------------------- 1 | ( function( window, atom, require, module, undefined ) { 2 | 'use strict'; 3 | 4 | // Imports 5 | var atomutils = require('atom'); 6 | var atommsgpanel = { 7 | MessagePanelView: null, 8 | PlainMessageView: null, 9 | LineMessageView: null 10 | } 11 | 12 | // Instance 13 | var commands = { 14 | subscriptions: new atomutils.CompositeDisposable(), 15 | panel: null, 16 | config: require( './config' ), 17 | childs: [], 18 | quickfix: { 19 | index: -1, 20 | error: [] 21 | } 22 | } 23 | 24 | commands.activate = function( state ) { 25 | 26 | // Define and update a command list from the user config 27 | var registeredAtomCommands = []; 28 | 29 | atom.config.observe( 'atom-shell-commands', {}, function( value ) { 30 | 31 | // Dispose old commands 32 | registeredAtomCommands.forEach( function( disposable ) { 33 | 34 | // Remove it from subscriptions and... 35 | commands.subscriptions.remove( disposable ); 36 | 37 | // ... dispose it manually 38 | disposable.dispose(); 39 | } ); 40 | 41 | registeredAtomCommands = []; 42 | 43 | if (value != null && value != undefined && 44 | value.commands != null && value.commands != undefined) { 45 | // Register new commands 46 | value.commands.forEach( function( command ) { 47 | 48 | // Create an atom command for each entry 49 | var commandName = 'atom-shell-commands:' + command.name; 50 | var commandSelector = command.selector || 'atom-workspace'; 51 | var atomCommand = atom.commands.add( commandSelector, commandName, function() { 52 | execute( command.command, command.arguments, command.options, command.matchs ); 53 | } ) 54 | 55 | // Create a menu entry for each command 56 | var menuEntry = atom.menu.add( [ { 57 | label : 'Packages', 58 | submenu: [ { 59 | label: 'Atom Shell Commands', 60 | submenu: [ 61 | { 62 | label: command.name, 63 | command: commandName 64 | } 65 | ] 66 | } ] 67 | } ] ); 68 | 69 | // Register it in the subscriptions; 70 | registeredAtomCommands.push( atomCommand ); 71 | registeredAtomCommands.push( menuEntry ); 72 | 73 | commands.subscriptions.add( atomCommand ); 74 | commands.subscriptions.add( menuEntry ); 75 | 76 | var options = command.options || {}; 77 | var keymap = options.keymap || ''; 78 | 79 | if (keymap) { 80 | const specifies = { 'atom-workspace': {} }; 81 | specifies['atom-workspace'][keymap] = commandName; 82 | var keyname = 'atom-shell-commands-keymap:' + command.name; 83 | var entry = atom.keymaps.add(keyname, specifies); 84 | 85 | registeredAtomCommands.push(entry); 86 | commands.subscriptions.add(entry); 87 | } 88 | } ); 89 | } 90 | 91 | } ); 92 | 93 | var where = 'atom-workspace'; 94 | var prefix = 'atom-shell-commands-config:'; 95 | var subscriptions = commands.subscriptions; 96 | 97 | subscriptions.add(atom.commands.add(where, prefix + 'toggle', function() { 98 | toggle(); 99 | })); 100 | 101 | subscriptions.add(atom.commands.add(where, prefix + 'stop', function() { 102 | childStop(); 103 | })); 104 | 105 | subscriptions.add(atom.commands.add(where, prefix + 'kill', function() { 106 | childKill(); 107 | })); 108 | 109 | subscriptions.add(atom.commands.add(where, prefix + 'error-first', function() { 110 | quickfixActive(0); 111 | })); 112 | 113 | subscriptions.add(atom.commands.add(where, prefix + 'error-last', function() { 114 | quickfixActive(commands.quickfix.error.length - 1); 115 | })); 116 | 117 | subscriptions.add(atom.commands.add(where, prefix + 'error-next', function() { 118 | if (commands.quickfix.index < commands.quickfix.error.length - 1) { 119 | quickfixActive(commands.quickfix.index + 1); 120 | } 121 | })); 122 | 123 | subscriptions.add(atom.commands.add(where, prefix + 'error-prev', function() { 124 | if (commands.quickfix.index > 0) { 125 | quickfixActive(commands.quickfix.index - 1); 126 | } 127 | })); 128 | } 129 | 130 | commands.deactivate = function() { 131 | if (commands.childs) { 132 | var pids = Object.keys(commands.childs); 133 | pids.forEach(function(pid) { 134 | var child = commands.childs[pid]; 135 | delete commands.childs[pid]; 136 | child.kill('SIGKILL'); 137 | }); 138 | } 139 | 140 | commands.subscriptions.dispose(); 141 | 142 | if (commands.panel) { 143 | commands.panel.remove(); 144 | commands.panel = null; 145 | } 146 | 147 | if (commands.quickfix) { 148 | commands.quickfix.error = []; 149 | commands.quickfix.index = -1; 150 | } 151 | } 152 | 153 | commands.serialize = function() { 154 | return {}; 155 | } 156 | 157 | function toggle() { 158 | messageInit(); 159 | if (commands.panel && commands.panel.panel && commands.panel.panel.isVisible()) { 160 | messageHide(); 161 | } else { 162 | messageShow(); 163 | } 164 | } 165 | 166 | function childStop() { 167 | var pids = Object.keys(commands.childs); 168 | pids.forEach(function(pid) { 169 | var child = commands.childs[pid]; 170 | child.kill(); 171 | }); 172 | } 173 | 174 | function childKill() { 175 | var pids = Object.keys(commands.childs); 176 | pids.forEach(function(pid) { 177 | var child = commands.childs[pid]; 178 | child.kill('SIGKILL'); 179 | delete commands.childs[pid]; 180 | }); 181 | } 182 | 183 | function messageInit() { 184 | if (!commands.panel) { 185 | var module = require ('atom-message-panel'); 186 | atommsgpanel.MessagePanelView = module.MessagePanelView; 187 | atommsgpanel.LineMessageView = module.LineMessageView; 188 | atommsgpanel.PlainMessageView = module.PlainMessageView; 189 | 190 | commands.panel = new atommsgpanel.MessagePanelView({ 191 | title: 'Atom Shell Commands', 192 | rawTitle: false, 193 | autoScroll: true, 194 | maxHeight: "130px" 195 | }); 196 | } 197 | } 198 | 199 | function messageShow() { 200 | messageInit(); 201 | if (commands.panel) { 202 | commands.panel.attach(); 203 | } 204 | } 205 | 206 | function messageHide() { 207 | messageInit(); 208 | if (commands.panel) { 209 | commands.panel.close(); 210 | } 211 | } 212 | 213 | function messageClear() { 214 | messageInit(); 215 | if (commands.panel) { 216 | commands.panel.clear(); 217 | } 218 | } 219 | 220 | function messagePlain(message, style) { 221 | if (!commands.panel) { 222 | messageInit(); 223 | } 224 | if (commands.panel) { 225 | var text = new atommsgpanel.PlainMessageView({ 226 | raw: false, 227 | message: message, 228 | className: style 229 | }); 230 | var position = commands.panel.body[0].scrollHeight; 231 | commands.panel.add(text); 232 | text.atompos = position - text.outerHeight(); 233 | return text; 234 | } 235 | return null; 236 | } 237 | 238 | function messageLine(file, line, column, message, style, preview) { 239 | if (!commands.panel) { 240 | messageInit(); 241 | } 242 | if (commands.panel) { 243 | var text = new atommsgpanel.LineMessageView({ 244 | file: file, 245 | line: line, 246 | column: column, 247 | message: message, 248 | className: style, 249 | preview: preview 250 | }); 251 | text.position.text(message) 252 | text.contents.text(''); 253 | text.position.addClass(style); 254 | text.position.removeClass('text-subtle'); 255 | //text.position.removeClass('inline-block'); 256 | var position = commands.panel.body[0].scrollHeight; 257 | commands.panel.add(text); 258 | text.atompos = position - text.outerHeight(); 259 | return text; 260 | } 261 | return null; 262 | } 263 | 264 | function updateScroll() { 265 | messageInit(); 266 | if (commands.panel) { 267 | commands.panel.updateScroll(); 268 | } 269 | } 270 | 271 | function updateTitle(title) { 272 | messageInit(); 273 | if (!commands.panel) return; 274 | if (!title) { 275 | commands.panel.setTitle('Atom Shell Commands'); 276 | } else { 277 | commands.panel.setTitle('Atom Shell Commands: ' + title); 278 | } 279 | } 280 | 281 | function quickfixReset() { 282 | if (commands.quickfix) { 283 | var quickfix = commands.quickfix; 284 | quickfix.index = -1; 285 | for (var i = 0; i < quickfix.error.length; i++) { 286 | var x = quickfix.error[i]; 287 | quickfix.error[i] = null; 288 | x.view = null; 289 | x.filename = null; 290 | x.style1 = null; 291 | x.style2 = null; 292 | x = null; 293 | } 294 | quickfix.error = []; 295 | } 296 | } 297 | 298 | function quickfixPush(view, filename, line, column, position, style) { 299 | var obj = { 300 | view: view, 301 | filename: filename, 302 | line: line, 303 | column: column, 304 | position: position, 305 | style: style, 306 | } 307 | if (commands.quickfix) { 308 | commands.quickfix.error.push(obj); 309 | } 310 | } 311 | 312 | function quickfixActive(index) { 313 | if (commands.panel == null) return -1; 314 | if (commands.panel.body == null) return -2; 315 | var quickfix = commands.quickfix; 316 | if (quickfix.index >= 0 && quickfix.index < quickfix.error.length) { 317 | var error = quickfix.error[quickfix.index]; 318 | var view = error.view; 319 | if (view) { 320 | view.removeClass("selected"); 321 | } 322 | quickfix.index = -1; 323 | } 324 | if (index < 0 || index >= quickfix.error.length) return -3; 325 | var error = quickfix.error[index]; 326 | commands.panel.body.scrollTop(error.position); 327 | if (error.view) { 328 | error.view.addClass("selected"); 329 | } 330 | quickfix.index = index; 331 | if (error.view) { 332 | error.view.goToLine(); 333 | } 334 | return 0; 335 | } 336 | 337 | // Execute an OS command 338 | function execute( command, args, options, matchs ) { 339 | if (options == null || options == undefined) options = {}; 340 | 341 | var mode = options.mode || ''; 342 | 343 | mode = (mode == '' && options.silent)? 'silent' : mode; 344 | mode = (mode == 'window' || mode == 'open')? 'terminal' : mode; 345 | mode = (mode != 'terminal' && mode != 'silent')? '' : mode; 346 | 347 | if (mode != 'terminal') { 348 | messageClear(); 349 | quickfixReset(); 350 | } 351 | 352 | if (mode == '') { 353 | messageShow(); 354 | } 355 | 356 | var env = getEnv(); 357 | if (env == null) { 358 | env = { 359 | FilePath: '', 360 | FileName: '', 361 | FileDir: '', 362 | FileExt: '', 363 | FileNameNoExt: '', 364 | ProjectDir: '', 365 | ProjectRel: '' 366 | } 367 | } 368 | 369 | var command = replace( command || '', env ); 370 | var args = replace( args || [], env ); 371 | var options = replace( options || {}, env ); 372 | var matchs = matchs || []; 373 | var cwd = options.cwd || ''; 374 | 375 | if (options.save == true) { 376 | var editor = atom.workspace.getActiveTextEditor(); 377 | if (editor) { 378 | try { 379 | editor.save(); 380 | } 381 | catch (e) { 382 | } 383 | } 384 | } 385 | 386 | // make a copy of args to avoid modify config 387 | var argv = [] 388 | for (var i = 0; i < args.length; i++) { 389 | // empty string in config.cson may turn into undefined 390 | argv.push((args[i] != null && args[i] != undefined)? args[i] : ''); 391 | } 392 | 393 | // Announcing launch 394 | var echo = JSON.stringify([command].concat(argv)); 395 | var text = "> " + command + ' ' + JSON.stringify( argv ) + ' cwd="' + cwd + '"'; 396 | 397 | if (mode != 'terminal') { 398 | messagePlain(echo, 'echo'); 399 | } 400 | 401 | var XRegExp = require('xregexp'); 402 | var path = require('path'); 403 | 404 | for (var i = 0; i < matchs.length; i++) { 405 | matchs[i] = XRegExp.XRegExp(matchs[i]); 406 | } 407 | 408 | function output(text, style) { 409 | for (var i = 0; i < matchs.length; i++) { 410 | var result = XRegExp.XRegExp.exec(text, matchs[i]); 411 | if (result == null) continue; 412 | if (!result.file) continue; 413 | var file = result.file; 414 | var line = parseInt(result.line || '1') || 1; 415 | var col = parseInt(result.col || '1') || 1; 416 | if (style == 'stdout') style = 'stdout-match'; 417 | else if (style == 'stderr') style = 'stderr-match'; 418 | if (!path.isAbsolute(file)) { 419 | file = path.join(cwd, file); 420 | } 421 | var text = messageLine(file, line, col, text, style); 422 | updateScroll(); 423 | if (text) { 424 | var position = text.atompos; 425 | if (position < 0) position = 0; 426 | quickfixPush(text, file, line, col, position, style); 427 | } 428 | return; 429 | } 430 | messagePlain(text, style); 431 | updateScroll(); 432 | } 433 | 434 | // sounds 435 | var sounds = require('./sounds'); 436 | 437 | // record time 438 | var millisec = (new Date()).getTime(); 439 | const spawn = require('child_process').spawn; 440 | const terminal = require('./terminal'); 441 | 442 | if (mode == "terminal") { 443 | terminal.open_terminal( command, argv, options ); 444 | //messagePlain("open new terminal to launch", "echo"); 445 | return; 446 | } 447 | 448 | // Run the spawn, we pass argv to make a shallow copy of the array because spawn will modify it. 449 | var proc = spawn( command, argv, options ); 450 | 451 | var stdout_cache = ''; 452 | var stderr_cache = ''; 453 | 454 | commands.childs[proc.pid] = proc; 455 | 456 | // Update console panel on data 457 | proc.stdout.on( 'data', function( data ) { 458 | stdout_cache += data; 459 | while (true) { 460 | var index = stdout_cache.indexOf('\n'); 461 | if (index < 0) break; 462 | var text = stdout_cache.substring(0, index + 1); 463 | stdout_cache = stdout_cache.substring(index + 1); 464 | output(text, 'stdout'); 465 | } 466 | } ); 467 | 468 | // Update console panel on error data 469 | proc.stderr.on( 'data', function( data ) { 470 | stderr_cache += data; 471 | while (true) { 472 | var index = stderr_cache.indexOf('\n'); 473 | if (index < 0) break; 474 | var text = stderr_cache.substring(0, index + 1); 475 | stderr_cache = stderr_cache.substring(index + 1); 476 | output(text, 'stderr'); 477 | } 478 | } ); 479 | 480 | proc.stdout.on( 'close', function( code, signal ) { 481 | // console.info('command closed', code, signal); 482 | if (stdout_cache.length > 0) { 483 | output(stdout_cache, 'stdout'); 484 | stdout_cache = ''; 485 | } 486 | } ); 487 | 488 | proc.stderr.on( 'close', function( code, signal ) { 489 | if (stderr_cache.length > 0) { 490 | output(stderr_cache, 'stderr'); 491 | stderr_cache = ''; 492 | } 493 | } ); 494 | 495 | // Register code for error 496 | proc.on('error', function(msg) { 497 | output(msg, 'error'); 498 | }); 499 | 500 | // Register code for termination 501 | proc.on('close', function(code) { 502 | var current = (new Date()).getTime(); 503 | var delta = (current - millisec) * 0.001; 504 | var style = 'echo'; 505 | if (code == null || code == undefined) { 506 | output('[Finished in ' + delta.toFixed(2) + ' seconds]', style); 507 | } else { 508 | if (code == 0) { 509 | output('[Finished in ' + delta.toFixed(2) + ' seconds]', style); 510 | } else { 511 | output('[Finished in ' + delta.toFixed(2) + ' seconds, with code ' + code.toString() + ']', style); 512 | } 513 | } 514 | 515 | if (proc.pid in commands.childs) { 516 | delete commands.childs[proc.pid]; 517 | } 518 | 519 | if (options.sound != undefined && options.sound != null && options.sound != '') { 520 | sounds.play(options.sound); 521 | } 522 | 523 | }); 524 | } 525 | 526 | // Generate Environment variables 527 | function getEnv() { 528 | var editor = atom.workspace.getActiveTextEditor(); 529 | if (editor == undefined || editor == null) 530 | return null; 531 | if (editor.getPath == undefined || editor.getPath == null) 532 | return null; 533 | 534 | var filepath = editor.getPath(); 535 | 536 | if (filepath == undefined || filepath == null) { 537 | return null; 538 | } 539 | 540 | var path = require('path'); 541 | var filename = path.basename(filepath); 542 | var filedir = path.dirname(filepath); 543 | 544 | var info = path.parse(filepath); 545 | var extname = info.ext; 546 | var mainname = info.name; 547 | var paths = atom.project.relativizePath(filepath); 548 | if (extname == undefined || extname == null) extname = ""; 549 | var selected = editor.getSelectedText() || ""; 550 | var position = editor.getCursorBufferPosition() || null; 551 | var curcol = (position)? position.column : 0; 552 | var currow = (position)? position.row : 0; 553 | var linetext = ""; 554 | var curword = ""; 555 | 556 | if (position) { 557 | var range = [[currow, 0], [currow, 1E10]]; 558 | linetext = editor.getTextInBufferRange(range) || ''; 559 | curword = editor.getWordUnderCursor(); 560 | } 561 | 562 | var env = { 563 | FilePath: filepath, 564 | FileName: filename, 565 | FileDir: filedir, 566 | FileExt: extname, 567 | FileNameNoExt: mainname, 568 | ProjectDir: (paths[0])? paths[0] : "", 569 | ProjectRel: paths[1], 570 | CurRow: currow, 571 | CurCol: curcol, 572 | CurSelected: selected, 573 | CurLineText: linetext, 574 | CurWord: curword 575 | }; 576 | 577 | return env; 578 | } 579 | 580 | // Replace members with env variables. 581 | function replace( input, vars ) { 582 | // Dispatch input type 583 | if ( !input ) { 584 | return; 585 | } else if ( typeof input == 'string' ) { 586 | return replaceString( input, vars ); 587 | } else if ( Array.isArray( input ) ) { 588 | return replaceArray( input, vars ); 589 | } else if ( typeof input == 'object' ) { 590 | return replaceObject( input, vars ); 591 | } else { 592 | return input; 593 | } 594 | } 595 | 596 | // replace a string with vars. 597 | function replaceString( input, vars ) { 598 | var keys = Object.keys(vars); 599 | keys.forEach( function(key) { 600 | var word = '{' + key + '}'; 601 | var value = vars[key]; 602 | input = input.split(word).join(value); 603 | }); 604 | return input; 605 | } 606 | 607 | // Replace array string elements with variables 608 | function replaceArray( input, vars ) { 609 | var output = new Array(input.length); 610 | for ( var i = 0; i < input.length; i++ ) { 611 | output[ i ] = replace( input[ i ], vars ); 612 | } 613 | return output; 614 | } 615 | 616 | // Replaces oboject string members with variables 617 | function replaceObject( input, vars ) { 618 | var output = {}; 619 | var keys = Object.keys( input ); 620 | keys.forEach( function( key ) { 621 | output[ key ] = replace( input[ key ], vars ); 622 | } ); 623 | return output; 624 | } 625 | 626 | // TODO: Register active processes for killing; 627 | 628 | // Publishing a reference 629 | module.exports = commands; 630 | 631 | } )( window, atom, require, module ); 632 | 633 | 634 | -------------------------------------------------------------------------------- /vendor/terminal.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | import sys, os, time 3 | import subprocess 4 | 5 | 6 | #---------------------------------------------------------------------- 7 | # configure 8 | #---------------------------------------------------------------------- 9 | class configure (object): 10 | 11 | def __init__ (self): 12 | self.dirhome = os.path.abspath(os.path.dirname(__file__)) 13 | self.diruser = os.path.abspath(os.path.expanduser('~')) 14 | self.unix = sys.platform[:3] != 'win' and True or False 15 | self.temp = os.environ.get('temp', os.environ.get('tmp', '/tmp')) 16 | self.tick = long(time.time()) % 100 17 | if self.unix: 18 | temp = os.environ.get('tmp', '/tmp') 19 | if not temp: 20 | temp = '/tmp' 21 | folder = os.path.join(temp, 'runner/folder') 22 | if not os.path.exists(folder): 23 | try: 24 | os.makedirs(folder, 0777) 25 | except: 26 | folder = '' 27 | if folder: 28 | self.temp = folder 29 | try: 30 | os.chmod(self.temp, 0777) 31 | except: 32 | pass 33 | self.temp = os.path.join(self.temp, 'winex_%02d.cmd'%self.tick) 34 | self.cygwin = '' 35 | self.GetShortPathName = None 36 | 37 | def call (self, args, stdin = None): 38 | p = subprocess.Popen(args, shell = False, 39 | stdin = subprocess.PIPE, 40 | stdout = subprocess.PIPE, 41 | stderr = subprocess.PIPE) 42 | if stdin != None: 43 | p.stdin.write(stdin) 44 | p.stdin.flush() 45 | p.stdin.close() 46 | stdout = p.stdout.read() 47 | stderr = p.stderr.read() 48 | code = p.wait() 49 | return code, stdout, stderr 50 | 51 | def where (self, filename, path = []): 52 | PATH = os.environ.get('PATH', '') 53 | if sys.platform[:3] == 'win': 54 | PATH = PATH.split(';') 55 | else: 56 | PATH = PATH.split(':') 57 | if path: 58 | PATH.extend(path) 59 | for base in PATH: 60 | path = os.path.join(base, filename) 61 | if os.path.exists(path): 62 | return path 63 | return None 64 | 65 | def escape (self, path): 66 | path = path.replace('\\', '\\\\').replace('"', '\\"') 67 | return path.replace('\'', '\\\'') 68 | 69 | def darwin_osascript (self, script): 70 | for line in script: 71 | #print line 72 | pass 73 | if type(script) == type([]): 74 | script = '\n'.join(script) 75 | p = subprocess.Popen(['/usr/bin/osascript'], shell = False, 76 | stdin = subprocess.PIPE, stdout = subprocess.PIPE, 77 | stderr = subprocess.STDOUT) 78 | p.stdin.write(script) 79 | p.stdin.flush() 80 | p.stdin.close() 81 | text = p.stdout.read() 82 | p.stdout.close() 83 | code = p.wait() 84 | #print text 85 | return code, text 86 | 87 | def darwin_open_system (self, title, script, profile = None): 88 | script = [ line for line in script ] 89 | script.insert(0, 'clear') 90 | fp = open(self.temp, 'w') 91 | fp.write('#! /bin/sh\n') 92 | for line in script: 93 | fp.write(line + '\n') 94 | fp.close() 95 | os.chmod(self.temp, 0777) 96 | cmd = self.where('open') 97 | self.call([cmd, '-a', 'Terminal', self.temp]) 98 | return 0, '' 99 | 100 | def darwin_open_terminal (self, title, script, profile = None): 101 | osascript = [] 102 | command = [] 103 | for line in script: 104 | if line.rstrip('\r\n\t ') == '': 105 | continue 106 | line = line.replace('\\', '\\\\') 107 | line = line.replace('"', '\\"') 108 | line = line.replace("'", "\\'") 109 | command.append(line) 110 | command.insert(0, 'clear') 111 | command = '; '.join(command) 112 | osascript.append('tell application "Terminal"') 113 | osascript.append(' if it is running then') 114 | osascript.append(' do script "%s; exit"'%command) 115 | osascript.append(' else') 116 | osascript.append(' do script "%s; exit" in window 1'%command) 117 | osascript.append(' end if') 118 | x = ' set current settings of selected tab of ' 119 | x += 'window 1 to settings set "%s"' 120 | if profile != None: 121 | osascript.append(x%profile) 122 | osascript.append(' activate') 123 | osascript.append('end tell') 124 | return self.darwin_osascript(osascript) 125 | 126 | def darwin_open_iterm (self, title, script, profile = None): 127 | osascript = [] 128 | command = [] 129 | script = [ line for line in script ] 130 | if profile: 131 | script.insert(0, 'clear') 132 | script.insert(0, 'echo "\033]50;SetProfile=%s\a"'%profile) 133 | for line in script: 134 | if line.rstrip('\r\n\t ') == '': 135 | continue 136 | line = line.replace('\\', '\\\\\\\\') 137 | line = line.replace('"', '\\\\\\"') 138 | line = line.replace("'", "\\\\\\'") 139 | command.append(line) 140 | command = '; '.join(command) 141 | osascript.append('tell application "iTerm"') 142 | osascript.append('set myterm to (make new terminal)') 143 | osascript.append('tell myterm') 144 | osascript.append('set mss to (make new session at the end of sessions)') 145 | osascript.append('tell mss') 146 | if title: 147 | osascript.append(' set name to "%s"'%self.escape(title)) 148 | osascript.append(' activate') 149 | osascript.append(' exec command "/bin/bash -c \\"%s\\""'%command) 150 | osascript.append('end tell') 151 | osascript.append('end tell') 152 | osascript.append('end tell') 153 | return self.darwin_osascript(osascript) 154 | 155 | def unix_escape (self, argument, force = False): 156 | argument = argument.replace('\\', '\\\\') 157 | argument = argument.replace('"', '\\"') 158 | argument = argument.replace("'", "\\'") 159 | return argument.replace(' ', '\\ ') 160 | 161 | def win32_escape (self, argument, force = False): 162 | if force == False and argument: 163 | clear = True 164 | for n in ' \n\r\t\v\"': 165 | if n in argument: 166 | clear = False 167 | break 168 | if clear: 169 | return argument 170 | output = '"' 171 | size = len(argument) 172 | i = 0 173 | while True: 174 | blackslashes = 0 175 | while (i < size and argument[i] == '\\'): 176 | i += 1 177 | blackslashes += 1 178 | if i == size: 179 | output += '\\' * (blackslashes * 2) 180 | break 181 | if argument[i] == '"': 182 | output += '\\' * (blackslashes * 2 + 1) 183 | output += '"' 184 | else: 185 | output += '\\' * blackslashes 186 | output += argument[i] 187 | i += 1 188 | output += '"' 189 | return output 190 | 191 | def win32_path_short (self, path): 192 | path = os.path.abspath(path) 193 | if self.unix: 194 | return path 195 | if not self.GetShortPathName: 196 | self.kernel32 = None 197 | self.textdata = None 198 | try: 199 | import ctypes 200 | self.kernel32 = ctypes.windll.LoadLibrary("kernel32.dll") 201 | self.textdata = ctypes.create_string_buffer('\000' * 1024) 202 | self.GetShortPathName = self.kernel32.GetShortPathNameA 203 | args = [ ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int ] 204 | self.GetShortPathName.argtypes = args 205 | self.GetShortPathName.restype = ctypes.c_uint32 206 | except: pass 207 | if not self.GetShortPathName: 208 | return path 209 | retval = self.GetShortPathName(path, self.textdata, 1024) 210 | shortpath = self.textdata.value 211 | if retval <= 0: 212 | return '' 213 | return shortpath 214 | 215 | # start cmd.exe in a new window and execute script 216 | def win32_open_console (self, title, script, profile = None): 217 | fp = open(self.temp, 'w') 218 | fp.write('@echo off\n') 219 | if title: 220 | fp.write('title %s\n'%self.win32_escape(title)) 221 | for line in script: 222 | fp.write(line + '\n') 223 | fp.close() 224 | fp = None 225 | pathname = self.win32_path_short(self.temp) 226 | os.system('start cmd /C %s'%(pathname)) 227 | return 0 228 | 229 | def darwin_open_xterm (self, title, script, profile = None): 230 | command = [] 231 | for line in script: 232 | if line.rstrip('\r\n\t ') == '': 233 | continue 234 | line = line.replace('\\', '\\\\') 235 | line = line.replace('"', '\\"') 236 | line = line.replace("'", "\\'") 237 | command.append(line) 238 | command = '; '.join(command) 239 | if title: 240 | command = 'xterm -T "%s" -e "%s" &'%(title, command) 241 | else: 242 | command = 'xterm -e "%s" &'%(command) 243 | subprocess.call(['/bin/sh', '-c', command]) 244 | return 0 245 | 246 | def linux_open_xterm (self, title, script, profile = None): 247 | command = [] 248 | for line in script: 249 | if line.rstrip('\r\n\t ') == '': 250 | continue 251 | line = line.replace('\\', '\\\\') 252 | line = line.replace('"', '\\"') 253 | line = line.replace("'", "\\'") 254 | command.append(line) 255 | command = '; '.join(command) 256 | cmdline = self.where('xterm') + ' ' 257 | if title: 258 | title = self.escape(title) 259 | cmdline += '-T "%s" '%title 260 | cmdline += '-e "%s" '%command 261 | os.system(cmdline + ' & ') 262 | return 0 263 | 264 | def linux_open_gnome (self, title, script, profile = None): 265 | command = [] 266 | for line in script: 267 | if line.rstrip('\r\n\t ') == '': 268 | continue 269 | line = line.replace('\\', '\\\\') 270 | line = line.replace('"', '\\"') 271 | line = line.replace("'", "\\'") 272 | command.append(line) 273 | command = '; '.join(command) 274 | command = '%s -c \"%s\"'%(self.where('bash'), command) 275 | cmdline = self.where('gnome-terminal') + ' ' 276 | if title: 277 | title = self.escape(title and title or '') 278 | cmdline += '-t "%s" '%title 279 | if profile: 280 | cmdline += '--window-with-profile="%s" '%profile 281 | cmdline += ' --command=\'%s\''%command 282 | os.system(cmdline) 283 | return 0 284 | 285 | def cygwin_open_cmd (self, title, script, profile = None): 286 | temp = os.environ.get('TEMP', os.environ.get('TMP', '/tmp')) 287 | filename = os.path.split(self.temp)[-1] 288 | cwd = os.getcwd() 289 | fp = open(os.path.join(temp, filename), 'w') 290 | fp.write('@echo off\n') 291 | if title: 292 | fp.write('title %s\n'%self.win32_escape(title)) 293 | for line in script: 294 | fp.write(line + '\n') 295 | fp.close() 296 | fp = None 297 | command = 'cygstart cmd /C %s'%(filename) 298 | # print script 299 | p = subprocess.Popen(['cygstart', 'cmd', '/C', filename], cwd = temp) 300 | p.wait() 301 | return 0 302 | 303 | def cygwin_write_script (self, filename, script): 304 | fp = open(filename, 'w') 305 | fp.write('#! /bin/sh\n') 306 | for line in script: 307 | fp.write('%s\n'%line) 308 | fp.close() 309 | fp = None 310 | return 0 311 | 312 | def cygwin_win_path (self, path): 313 | code, stdout, stderr = self.call(['cygpath', '-w', path]) 314 | return stdout.strip('\r\n') 315 | 316 | def cygwin_open_bash (self, title, script, profile = None): 317 | filename = os.path.split(self.temp)[-1] 318 | scriptname = os.path.join('/tmp', filename) 319 | script = [ n for n in script ] 320 | script.insert(0, 'cd %s'%self.unix_escape(os.getcwd())) 321 | self.cygwin_write_script(scriptname, script) 322 | command = ['cygstart', 'bash'] 323 | if profile == 'login': 324 | command.append('--login') 325 | self.call(command + ['-i', scriptname]) 326 | return 0 327 | 328 | def cygwin_open_mintty (self, title, script, profile = None): 329 | filename = os.path.split(self.temp)[-1] 330 | scriptname = os.path.join('/tmp', filename) 331 | script = [ n for n in script ] 332 | script.insert(0, 'cd %s'%self.unix_escape(os.getcwd())) 333 | self.cygwin_write_script(scriptname, script) 334 | command = ['cygstart', 'mintty'] 335 | # if title: 336 | # command += ['-t', title] 337 | if os.path.exists('/Cygwin-Terminal.ico'): 338 | command += ['-i', '/Cygwin-Terminal.ico'] 339 | command += ['-e', 'bash'] 340 | if profile == 'login': 341 | command.append('--login') 342 | command.extend(['-i', scriptname]) 343 | self.call(command) 344 | return 0 345 | 346 | # convert windows path to cygwin path 347 | def win2cyg (self, path): 348 | path = os.path.abspath(path) 349 | return '/cygdrive/%s%s'%(path[0], path[2:].replace('\\', '/')) 350 | 351 | # convert cygwin path to windows path 352 | def cyg2win (self, path): 353 | if path[1:2] == ':': 354 | return os.path.abspath(path) 355 | if path.lower().startswith('/cygdrive/'): 356 | path = path[10] + ':' + path[11:] 357 | return path 358 | if not path.startswith('/'): 359 | raise Exception('cannot convert path: %s'%path) 360 | if not self.cygwin: 361 | raise Exception('cannot find cygwin root') 362 | if sys.platform == 'cygwin': 363 | return self.cygwin_win_path(path) 364 | return os.path.abspath(os.path.join(self.cygwin, path[1:])) 365 | 366 | # use bash in cygwin to execute script and return output 367 | def win32_cygwin_execute (self, script, login = False): 368 | if not self.cygwin: 369 | return -1, None 370 | if not os.path.exists(self.cygwin): 371 | return -2, None 372 | if not os.path.exists(os.path.join(self.cygwin, 'bin/sh.exe')): 373 | return -3, None 374 | bash = os.path.join(self.cygwin, 'bin/bash') 375 | filename = os.path.split(self.temp)[-1] 376 | tempfile = os.path.join(self.cygwin, 'tmp/' + filename) 377 | fp = open(tempfile, 'wb') 378 | fp.write('#! /bin/sh\n') 379 | path = self.win2cyg(os.getcwd()) 380 | fp.write('cd %s\n'%self.unix_escape(path)) 381 | for line in script: 382 | fp.write('%s\n'%line) 383 | fp.close() 384 | command = [bash] 385 | if login: 386 | command.append('--login') 387 | command.extend(['-i', '/tmp/' + filename]) 388 | p = subprocess.Popen(command, shell = False, 389 | stdout = subprocess.PIPE, stderr = subprocess.STDOUT) 390 | text = p.stdout.read() 391 | p.stdout.close() 392 | code = p.wait() 393 | return code, text 394 | 395 | # use bash in cygwin to execute script and output to current cmd window 396 | def win32_cygwin_now (self, script, login = False): 397 | if not self.cygwin: 398 | return -1, None 399 | if not os.path.exists(self.cygwin): 400 | return -2, None 401 | if not os.path.exists(os.path.join(self.cygwin, 'bin/sh.exe')): 402 | return -3, None 403 | bash = os.path.join(self.cygwin, 'bin/bash') 404 | filename = os.path.split(self.temp)[-1] 405 | tempfile = os.path.join(self.cygwin, 'tmp/' + filename) 406 | fp = open(tempfile, 'wb') 407 | fp.write('#! /bin/sh\n') 408 | path = self.win2cyg(os.getcwd()) 409 | fp.write('cd %s\n'%self.unix_escape(path)) 410 | for line in script: 411 | fp.write('%s\n'%line) 412 | fp.close() 413 | command = [bash] 414 | if login: 415 | command.append('--login') 416 | command.extend(['-i', '/tmp/' + filename]) 417 | subprocess.call(command, shell = False) 418 | return 0 419 | 420 | # open bash of cygwin in a new window and execute script 421 | def win32_cygwin_open_bash (self, title, script, profile = None): 422 | if not self.cygwin: 423 | return -1, None 424 | if not os.path.exists(self.cygwin): 425 | return -2, None 426 | if not os.path.exists(os.path.join(self.cygwin, 'bin/sh.exe')): 427 | return -3, None 428 | bash = os.path.join(self.cygwin, 'bin/bash.exe') 429 | filename = os.path.split(self.temp)[-1] 430 | tempfile = os.path.join(self.cygwin, 'tmp/' + filename) 431 | fp = open(tempfile, 'wb') 432 | fp.write('#! /bin/sh\n') 433 | path = self.win2cyg(os.getcwd()) 434 | fp.write('cd %s\n'%self.unix_escape(path)) 435 | for line in script: 436 | fp.write('%s\n'%line) 437 | fp.close() 438 | short_bash = self.win32_path_short(bash) 439 | command = 'start %s '%short_bash 440 | command += '--login -i /tmp/' + filename 441 | os.system(command) 442 | return 0 443 | 444 | # open mintty of cygwin in a new window and execute script 445 | def win32_cygwin_open_mintty (self, title, script, profile = None): 446 | if not self.cygwin: 447 | return -1, None 448 | if not os.path.exists(self.cygwin): 449 | return -2, None 450 | if not os.path.exists(os.path.join(self.cygwin, 'bin/sh.exe')): 451 | return -3, None 452 | mintty = os.path.join(self.cygwin, 'bin/mintty.exe') 453 | filename = os.path.split(self.temp)[-1] 454 | tempfile = os.path.join(self.cygwin, 'tmp/' + filename) 455 | fp = open(tempfile, 'wb') 456 | fp.write('#! /bin/sh\n') 457 | path = self.win2cyg(os.getcwd()) 458 | fp.write('cd %s\n'%self.unix_escape(path)) 459 | for line in script: 460 | fp.write('%s\n'%line) 461 | fp.close() 462 | shortname = self.win32_path_short(mintty) 463 | command = 'start %s '%shortname 464 | if os.path.exists(os.path.join(self.cygwin, 'Cygwin-Terminal.ico')): 465 | command += '-i /Cygwin-Terminal.ico ' 466 | if title: 467 | command += '-t "%s" '%title 468 | command += '-e /usr/bin/bash ' 469 | if profile == 'login' or True: 470 | command += '--login ' 471 | command += '-i /tmp/' + filename 472 | # print command 473 | os.system(command) 474 | return 0 475 | 476 | 477 | #---------------------------------------------------------------------- 478 | # die 479 | #---------------------------------------------------------------------- 480 | def die(message): 481 | sys.stderr.write('%s\n'%message) 482 | sys.stderr.flush() 483 | sys.exit(0) 484 | return 0 485 | 486 | 487 | 488 | #---------------------------------------------------------------------- 489 | # terminal class 490 | #---------------------------------------------------------------------- 491 | class Terminal (object): 492 | 493 | def __init__ (self): 494 | self.config = configure() 495 | self.unix = sys.platform[:3] != 'win' and True or False 496 | self.cygwin_login = False 497 | self.post_command = '' 498 | 499 | def __win32_open_terminal (self, terminal, title, script, profile): 500 | if terminal in ('', 'system', 'dos', 'win', 'windows', 'command', 'cmd'): 501 | self.config.win32_open_console(title, script) 502 | elif terminal in ('cygwin', 'bash', 'mintty', 'cygwin-mintty', 'cygwinx'): 503 | if not self.config.cygwin: 504 | die('please give cygwin path in profile') 505 | return -1 506 | if not os.path.exists(self.config.cygwin): 507 | die('can not find cygwin in: %s'%self.config.cygwin) 508 | return -2 509 | if not os.path.exists(os.path.join(self.config.cygwin, 'bin/sh.exe')): 510 | die('can not verify cygwin in: %s'%self.config.cygwin) 511 | return -3 512 | if terminal in ('cygwin', 'bash'): 513 | self.config.win32_cygwin_open_bash(title, script, profile) 514 | elif terminal in ('cygwin-silent', 'cygwin-shell', 'cygwinx'): 515 | self.config.win32_cygwin_now(script, True) 516 | else: 517 | self.config.win32_cygwin_open_mintty(title, script, profile) 518 | else: 519 | die('bad terminal name: %s'%terminal) 520 | return -4 521 | return 0 522 | 523 | def __cygwin_open_terminal (self, terminal, title, script, profile): 524 | if terminal in ('dos', 'win', 'cmd', 'command', 'system', 'windows'): 525 | self.config.cygwin_open_cmd(title, script, profile) 526 | elif terminal in ('bash', 'sh', '', 'default'): 527 | self.config.cygwin_open_bash(title, script, profile) 528 | elif terminal in ('mintty', 'cygwin-mintty'): 529 | if not title: 530 | title = 'Cygwin Mintty' 531 | self.config.cygwin_open_mintty(title, script, profile) 532 | else: 533 | die('bad terminal name: %s'%terminal) 534 | return -1 535 | return 0 536 | 537 | def __darwin_open_terminal (self, terminal, title, script, profile): 538 | if terminal in ('', 'system', 'default'): 539 | if (not profile) and (not title): 540 | self.config.darwin_open_system(title, script, profile) 541 | else: 542 | self.config.darwin_open_terminal(title, script, profile) 543 | elif terminal in ('terminal',): 544 | self.config.darwin_open_terminal(title, script, profile) 545 | elif terminal in ('iterm', 'iterm2'): 546 | self.config.darwin_open_iterm(title, script, profile) 547 | elif terminal in ('xterm', 'x'): 548 | self.config.darwin_open_xterm(title, script, profile) 549 | else: 550 | die('bad terminal name: %s'%terminal) 551 | return -1 552 | return 0 553 | 554 | def __linux_open_terminal (self, terminal, title, script, profile): 555 | if terminal in ('xterm', '', 'default', 'system', 'x'): 556 | self.config.linux_open_xterm(title, script, profile) 557 | elif terminal in ('gnome', 'gnome-terminal'): 558 | self.config.linux_open_gnome(title, script, profile) 559 | else: 560 | die('bad terminal name: %s'%terminal) 561 | return -1 562 | return 0 563 | 564 | def open_terminal (self, terminal, title, script, profile): 565 | if terminal == None: 566 | terminal = '' 567 | if sys.platform[:3] == 'win': 568 | if script == None: 569 | return ('cmd (default)', 'cygwin', 'mintty', 'cygwinx') 570 | return self.__win32_open_terminal(terminal, title, script, profile) 571 | elif sys.platform == 'cygwin': 572 | if script == None: 573 | return ('bash (default)', 'mintty', 'windows') 574 | return self.__cygwin_open_terminal(terminal, title, script, profile) 575 | elif sys.platform == 'darwin': 576 | if script == None: 577 | return ('terminal (default)', 'iterm') 578 | return self.__darwin_open_terminal(terminal, title, script, profile) 579 | else: 580 | if script == None: 581 | return ('xterm (default)', 'gnome-terminal') 582 | return self.__linux_open_terminal(terminal, title, script, profile) 583 | return 0 584 | 585 | def check_windows (self, terminal): 586 | if sys.platform[:3] == 'win': 587 | if terminal == None: 588 | return True 589 | if terminal in ('', 'system', 'dos', 'win', 'windows', 'command', 'cmd'): 590 | return True 591 | elif sys.platform == 'cygwin': 592 | if terminal in ('dos', 'win', 'cmd', 'command', 'system', 'windows'): 593 | return True 594 | return False 595 | 596 | def execute (self, terminal, title, script, cwd, wait, profile): 597 | lines = [ line for line in script ] 598 | windows = self.check_windows(terminal) 599 | script = [] 600 | if cwd == None: 601 | cwd = os.getcwd() 602 | if terminal == None: 603 | terminal = '' 604 | if sys.platform[:3] == 'win' and cwd[1:2] == ':': 605 | if terminal in ('', 'system', 'dos', 'win', 'windows', 'command', 'cmd'): 606 | script.append(cwd[:2]) 607 | script.append('cd "%s"'%cwd) 608 | else: 609 | script.append('cd "%s"'%self.config.win2cyg(cwd)) 610 | elif sys.platform == 'cygwin': 611 | if terminal in ('dos', 'win', 'cmd', 'command', 'system', 'windows'): 612 | path = self.config.cyg2win(os.path.abspath(cwd)) 613 | script.append(path[:2]) 614 | script.append('cd "%s"'%path) 615 | else: 616 | script.append('cd "%s"'%cwd) 617 | else: 618 | script.append('cd "%s"'%cwd) 619 | script.extend(lines) 620 | if wait: 621 | if windows: 622 | script.append('pause') 623 | else: 624 | script.append('read -n1 -rsp "press any key to confinue ..."') 625 | if self.post_command: 626 | script.append(self.post_command) 627 | return self.open_terminal(terminal, title, script, profile) 628 | 629 | def run_command (self, terminal, title, command, cwd, wait, profile): 630 | script = [ command ] 631 | return self.execute(terminal, title, script, cwd, wait, profile) 632 | 633 | def run_tee (self, command, teename, shell = False, wait = False): 634 | args = [] 635 | for n in command: 636 | if sys.platform[:3] == 'win': 637 | n = self.config.win32_escape(n) 638 | else: 639 | n = self.config.unix_escape(n) 640 | args.append(n) 641 | import subprocess 642 | p = subprocess.Popen(args, stdin = None, stdout = subprocess.PIPE, \ 643 | stderr = subprocess.STDOUT, shell = shell) 644 | if sys.platform[:3] != 'win' and '~' in teename: 645 | teename = os.path.expanduser(teename) 646 | f = open(teename, 'w') 647 | while True: 648 | text = p.stdout.readline() 649 | if text in ('', None): 650 | break 651 | f.write(text) 652 | f.flush() 653 | sys.stdout.write(text) 654 | sys.stdout.flush() 655 | p.stdout.close() 656 | p.wait() 657 | f.close() 658 | if wait: 659 | if sys.platform[:3] == 'win': 660 | os.system('pause') 661 | else: 662 | os.system('read -n1 -rsp "press any key to continue ..."') 663 | return 0 664 | 665 | 666 | 667 | #---------------------------------------------------------------------- 668 | # main routine 669 | #---------------------------------------------------------------------- 670 | def main(argv = None, shellscript = None): 671 | if argv == None: 672 | argv = sys.argv 673 | argv = [ n for n in argv ] 674 | args = [] 675 | cmds = [] 676 | skip = ['-h', '--help', '-w', '-s'] 677 | index = 1 678 | stdin = False 679 | if len(argv) > 0: 680 | args.append(argv[0]) 681 | while index < len(argv): 682 | data = argv[index] 683 | if data in ('-s', '--stdin'): 684 | stdin = True 685 | if data[:2] == '--': 686 | args.append(data) 687 | index += 1 688 | elif data in skip: 689 | args.append(data) 690 | index += 1 691 | elif data[:1] == '-': 692 | args.append(data) 693 | index += 1 694 | if index >= len(argv): 695 | break 696 | args.append(argv[index]) 697 | index += 1 698 | else: 699 | cmds = argv[index:] 700 | break 701 | terminal = Terminal() 702 | help = terminal.open_terminal('', '', None, '') 703 | text = 'available terminal: ' 704 | text += ', '.join(help) 705 | import optparse 706 | if len(cmds) == 0 and len(args) > 0 and stdin == False: 707 | args.append('--help') 708 | elif stdin and len(cmds) > 0 and len(args) > 1: 709 | args.append('--help') 710 | desc = 'Execute program in a new terminal window' 711 | parser = optparse.OptionParser( \ 712 | usage = 'usage: %prog [options] command [args ...]', 713 | version = '0.0.0', 714 | description = desc) 715 | parser.add_option('-t', '--title', dest = 'title', default = None, 716 | help = 'title of new window') 717 | parser.add_option('-m', '--terminal', dest = 'terminal', default = None, 718 | help = text) 719 | parser.add_option('-p', '--profile', dest = 'profile', default = None, 720 | help = 'terminal profile') 721 | parser.add_option('-d', '--cwd', dest = 'cwd', default = '', 722 | help = 'working directory') 723 | parser.add_option('-w', '--wait', dest = 'wait', default = False, 724 | action = 'store_true', help = 'wait before exit') 725 | parser.add_option('-o', '--post', dest = 'post', default = '', 726 | help = 'post action') 727 | parser.add_option('-s', '--stdin', dest = 'stdin', default = False, 728 | action = 'store_true', help = 'read commands from stdin') 729 | parser.add_option('-e', '--tee', dest = 'tee', default = '', 730 | help = 'redirect output to file') 731 | if sys.platform[:3] == 'win': 732 | parser.add_option('-c', '--cygwin', dest = 'cygwin', default = '', 733 | help = 'cygwin home path when using cygwin terminal') 734 | opts, _ = parser.parse_args(args) 735 | if not opts.cwd: 736 | opts.cwd = os.getcwd() 737 | command = [] 738 | if sys.platform[:3] == 'win': 739 | cygwin = opts.cygwin 740 | terminal.config.cygwin = cygwin 741 | if shellscript: 742 | script = [ line for line in shellscript ] 743 | if opts.post: 744 | terminal.post_command = opts.post 745 | terminal.execute(opts.terminal, opts.title, script, 746 | opts.cwd, opts.wait, opts.profile) 747 | elif opts.stdin: 748 | text = '' 749 | while True: 750 | hr = sys.stdin.read() 751 | if hr == '': break 752 | text += hr 753 | script = text.split('\n') 754 | if opts.post: 755 | terminal.post_command = opts.post 756 | terminal.execute(opts.terminal, opts.title, script, 757 | opts.cwd, opts.wait, opts.profile) 758 | elif opts.tee != '': 759 | shell = False 760 | if sys.platform[:3] == 'win': 761 | shell = True 762 | terminal.run_tee(cmds, opts.tee, shell, opts.wait) 763 | else: 764 | for n in cmds: 765 | if terminal.check_windows(opts.terminal): 766 | n = terminal.config.win32_escape(n) 767 | else: 768 | n = terminal.config.unix_escape(n) 769 | command.append(n) 770 | command = ' '.join(command) 771 | if opts.post: 772 | terminal.post_command = opts.post 773 | terminal.run_command(opts.terminal, opts.title, command, 774 | opts.cwd, opts.wait, opts.profile) 775 | return 0 776 | 777 | 778 | #---------------------------------------------------------------------- 779 | # run clever for vimmake 780 | #---------------------------------------------------------------------- 781 | def vimtool(): 782 | 783 | return 0 784 | 785 | 786 | #---------------------------------------------------------------------- 787 | # testing casen 788 | #---------------------------------------------------------------------- 789 | if __name__ == '__main__': 790 | def test1(): 791 | cfg = configure() 792 | cfg.darwin_open_terminal('111', ['ls -la /', 'read -n1 -rsp press\\ any\\ key\\ to\\ continue\\ ...', 'echo "fuck you"']) 793 | 794 | def test2(): 795 | args = [ 'terminal', '-h' ] 796 | #args = [ 'terminal', '-w', '--terminal=cmd', '--cwd=e:/lesson', '--cygwin=d:/linux', '--title=fuck', 'DIR'] 797 | main(args) 798 | return 0 799 | 800 | def test3(): 801 | args = [ 'terminal', '-w', '--terminal=cmd', '--stdin' ] 802 | main(args) 803 | return 0 804 | 805 | #test2() 806 | main() 807 | 808 | 809 | 810 | --------------------------------------------------------------------------------