diff --git a/.gitignore b/.gitignore index 1a353f5..2b613de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,14 @@ build +test + +*.pyc +__pycache__ + +*.log + +__pycache__ .DS_Store + npm-debug.log node_modules diff --git a/CHANGELOG.md b/CHANGELOG.md index 1674837..835cc21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,40 @@ +## 0.9.4 +* Fixed inconsistency due to editor.save becoming async + +## 0.8.1 +* Fixed python3 import error + +## 0.8.0 +* cp is now optional +* Updated and refactored terminal.js to function with python version of cp +* Ported cp from C to Python +* Added new features to cp +* Made cross-platform compatible +* Ported cp from C++ to C +* Added a niche library for cp + +## 0.7.3 +* `{dir}` `{name}` `{ext}` is now supported. You can use `python.exe "{dir}\{name}{ext}"`, which is equal to `python.exe "{file}"`. + +## 0.7.2 +* Trying to fix bug: cannot find file in some environments + +## 0.7.0 (skipped due to apm bug) and 0.7.1 +* Fix bug: multiple consoles +* New feature: add command setting with f5 and f6 + +## 0.6.2 +* Fix bug: Uncaught TypeError: Cannot read property 'path' of null + +## 0.6.1 +* create an option to disable notifications of failure, false by default + +## 0.6.0 +* create an option to disable notifications of success, false by default + +## 0.5.2 +* force set cwd + ## 0.5.0 * thanks to Frezzu for fixing the missing dll bug diff --git a/LICENSE.md b/LICENSE.md index 5f3f537..4f6c6ed 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2016 +Copyright (c) 2016 foreshadow (a.k.a. Infinity) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 9bc4039..a933ed2 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,77 @@ # atom-python-run package +[![GitHub issues](https://img.shields.io/github/issues/foreshadow/atom-python-run.svg?style=plastic)](https://github.com/foreshadow/atom-python-run/issues) +[![GitHub stars](https://img.shields.io/github/stars/foreshadow/atom-python-run.svg?style=plastic)](https://github.com/foreshadow/atom-python-run/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/foreshadow/atom-python-run.svg?style=plastic)](https://github.com/foreshadow/atom-python-run/network) +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=plastic)](https://raw.githubusercontent.com/foreshadow/atom-python-run/master/LICENSE.md) -Run your python source file (.py) using `F5` or `F6`! +Run your python (.py) source file using `F5` or `F6`! -# Prerequisite +![](https://cloud.githubusercontent.com/assets/2712675/18710388/9a665ed8-8037-11e6-803a-35e4555e89d0.jpg) -- Windows -- Add the directory of python.exe to ```PATH```. +# Contents +- [Prerequisite](#prerequisite) +- [Features](#features) +- [Compatability](#compatibility) +- [Documentation](#documentation) +- [Issues](#issues) +- [Development](#development) -# Method of use +# Prerequisite -1. Open a .py file. -2. Hit `F5` or `F6` to run. +- Atom Text Editor (nightly or latest stable release) +- Python 2 and/or 3 +- Add Python (and any other interpreters) to the `PATH` environment variable. # Features -- Using `python.exe` - - almost the same console with python IDLE +- Using `python` + - Almost the same console with python IDLE, which provides syntax error and runtime error messages. - CodeBlocks debug console style - - shows return value and execution time + - Shows return value and execution time + - It is a rough time based on real time rather than CPU kernel / user time + +# Compatibility + +- Cross Platform Compatible + - Runs on Windows, Mac OS X, and Linux + +- True Arbitrary Execution + - Global python is the default interpreter + - Execute using any interpreter + - Pass options to the given interpreter + - Pass arguments to the program to be executed + +- Python 2 and 3 + - Note: If you have problems executing, you can install a global version of latest `python2.7.x` (even if you have `python3.x.x` installed). Please report any `python3` issues if you want to avoid installing a global `python2` version. + +# Documentation + +This project has been documented in a fair amount of detail over time. This documentation can be found in the [Wiki](https://github.com/foreshadow/atom-python-run/wiki). +**Everyone** should take the time to reveiw the [Wiki README](https://github.com/foreshadow/atom-python-run/wiki/00-Readme) at the bare minimum. It details an overview on how to handle issues, use different versions, and includes links to primary sections of the Wiki. + +**Everyone** should also take the time to review the Wiki section [How do I use atom-python-run?](https://github.com/foreshadow/atom-python-run/wiki/12-How-Do-I-Use-atom-python-run). It covers everything from installation, to configuration, logging, and much more. You just might be surprised by what you can do with atom-python-run. + +You should have the basics after having covered both the *README* and *How Do I use atom-python-run?* sections. Most FAQ's can be resolved by simply reading them. The guides provided should allow us to help you with what ever issue you're facing. + +**NOTE**: Be sure to read the [Wiki](https://github.com/foreshadow/atom-python-run/wiki) and the [Wiki README](https://github.com/foreshadow/atom-python-run/wiki/00-Readme) before reporting an issue or making a pull request. A lot of time has been put in to it to help you the user (or dev) get started and on your way. + +# Issues +- Before newing an issue, check to see if someone else is experiencing any related issues. +- Check to see if any issues that were closed resemble your issue and re-open it addressing that you're experiencing a similar issue. +- Provide details about your issue, such as errors and/or logs. +- Provide reproduction steps (we can't help you if we don't know how to reproduce the error!). + +# Development + +If you're a developer and are interested in this project you can find this repos API's in the [Wiki](https://github.com/foreshadow/atom-python-run/wiki). More specifically, you'll want to take a look at [How does the cp module work?](https://github.com/foreshadow/atom-python-run/wiki#how-does-the-cp-module-work) and [How does the terminal.js module work?](https://github.com/foreshadow/atom-python-run/wiki#how-does-the-terminal-module-work) sections of the Wiki. + +You can also just read the key source files +- [cp](https://github.com/foreshadow/atom-python-run/tree/master/cp) (cp is written in python) +- [terminal.js](https://github.com/foreshadow/atom-python-run/blob/master/lib/terminal.js) +- [atom-python-run.js](https://github.com/foreshadow/atom-python-run/blob/master/lib/atom-python-run.js) +- New an issue if you have any idea of new features. ![A screenshot of your package](https://f.cloud.github.com/assets/69169/2290250/c35d867a-a017-11e3-86be-cd7c5bf3ff9b.gif) + +This is a package for Atom diff --git a/bin/cp.exe b/bin/cp.exe deleted file mode 100644 index 6d7232a..0000000 Binary files a/bin/cp.exe and /dev/null differ diff --git a/bin/libgcc_s_dw2-1.dll b/bin/libgcc_s_dw2-1.dll deleted file mode 100644 index 9e32dc2..0000000 Binary files a/bin/libgcc_s_dw2-1.dll and /dev/null differ diff --git a/cp/cp/__init__.py b/cp/cp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cp/cp/parse.py b/cp/cp/parse.py new file mode 100644 index 0000000..f5b42e4 --- /dev/null +++ b/cp/cp/parse.py @@ -0,0 +1,146 @@ +"""parser - handles arbitrary arguments to be executed.""" +# +# NOTE: 2to3 and 3to2 Compatability +# importing should consider 2to3 and 3to2 implications. +# +# https://stackoverflow.com/questions/85451/python-time-clock-vs-time-time-accuracy#85533 +# https://docs.python.org/3/library/time.html#time.time +# https://docs.python.org/2.7/library/time.html#time.clock +# https://docs.python.org/2/library/timeit.html +# https://docs.python.org/2/library/sys.html#sys.platform +# https://docs.python.org/2/library/string.html#formatstrings +# https://docs.python.org/3.6/howto/argparse.html +# https://docs.python.org/3.6/library/argparse.html +# +from __future__ import absolute_import +from __future__ import print_function +from os import system, environ +from os.path import isdir, dirname +from subprocess import call +from time import time +from sys import platform +import argparse +import logging + + +__all__ = ("Parser",) + + +class Base(object): + def __init__(self): + self._parser = None + self._lexer = None + self._namespace = dict() + self._command = list() + self._logPath = str() + self._exitCode = None + self._clock = None + + def setLexer(self, *args, **kwargs): + self._parser = argparse.ArgumentParser(**kwargs) + self._parser.add_argument('-f', '--file', help="pipes 'stdout' and 'stderr' to the given file") + self._parser.add_argument('-p', '--pause', action='store_true', default=False, help="prompt client for 'stdin'") + self._parser.add_argument('repl', help="the interpreter used to execute 'args'") + self._parser.add_argument('args', nargs='+', help="the arugments that are passed to 'repl'") + self._lexer = self._parser.parse_args(*args[1:]) + + def getLexer(self): + return self._lexer + + def setNamespace(self): + self._namespace = vars(self._lexer) + + def getNamespace(self): + return self._namespace + + +class Logpath(Base): + def logLogPath(self): + logging.debug('Log Path: %s', self._logPath) + + def hasLogPath(self): + return isdir(dirname(self._logPath)) + + def defaultLogPath(self): + if 'win32' == platform: + return "{}\\.atom\\packages\\atom-python-run\\cp.log".format(environ['USERPROFILE']) + return "{}/.atom/packages/atom-python-run/cp.log".format(environ['HOME']) + + def setLogPath(self, path=None): + if path is None: + self._logPath = self.defaultLogPath() + else: + self._logPath = path + + def getLogPath(self): + if not self.hasLogPath(): + self._logPath = "cp.log" + return self._logPath + + +class Clock(Base): + def getClock(self): + return self._clock + + def logClock(self): + logging.info('return code: %d (0x%x)', self._exitCode, self._exitCode) + logging.info('elapsed time: %.6f', self._clock) + + def printClock(self): + print("\nProcess returned {:d} (0x{:x})".format( + self._exitCode, self._exitCode), end="\t") + print("execution time : {:.3f} s".format(self._clock)) + + +class Command(Base): + def _setRepl(self): + self._command.append(self._namespace['repl']) + + def _setArgs(self): + for arg in self._namespace['args']: + self._command.append(arg) + + def _logCommand(self): + logging.debug('command: %s', self._command) + + def _logNamespace(self): + for key, value in self._namespace.items(): + logging.debug('%s: %s', key, value) + + def setCommand(self): + self._setRepl() + self._setArgs() + self._logCommand() + self._logNamespace() + + def getCommand(self): + return self._command + + +class Call(Base): + def pipeCall(self, mode='a'): + with open(self._namespace['file'], mode) as stream: + self._clock = time() + self._exitCode = call(self._command, stdout=stream, stderr=stream) + self._clock = time() - self._clock + f.close() + + def call(self): + self._clock = time() + self._exitCode = call(self._command) + self._clock = time() - self._clock + + def exitCode(self): + return self._exitCode + + +class Parser(Command, Call, Clock, Logpath): + def pause(self): + if 'win32' == platform: + system('pause') + elif 'darwin' == platform: + system('echo "Close this window to continue..."') + elif 'linux' == platform[:5]: + system("printf 'Press [ENTER] to continue...'; read _;") + else: + logging.info('Unknown OS Type: %s', platform) diff --git a/cp/cp/utils.py b/cp/cp/utils.py new file mode 100644 index 0000000..098a19a --- /dev/null +++ b/cp/cp/utils.py @@ -0,0 +1,10 @@ + +from sys import platform +from os import system + + +def clear(): + if 'win32' == platform: + system('cls') + else: + system('clear') diff --git a/cp/main.py b/cp/main.py new file mode 100644 index 0000000..28b093a --- /dev/null +++ b/cp/main.py @@ -0,0 +1,79 @@ +# Copyright (C) 2017 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# +# +# NOTE: basicConfig is only called once +# +# NOTE: basicConfig()s filemode parameter +# filemode is set to write to avoid an overly sized log file. +# +# if the file is truncated in such a way that is unsuitable, +# just remove the filemode argument from the basicConfig call. +# by default, filemode is already set to append. +# +from __future__ import absolute_import +from __future__ import print_function +from sys import argv, platform +from logging import basicConfig, DEBUG, info +from cp.parse import Parser +from cp.utils import clear + + +if __name__ == '__main__': + version_info = "v0.3.0" + + parser = Parser() + + parser.setLogPath() + + logpath = parser.getLogPath() + + basicConfig( + filename=logpath, + filemode='w', + level=DEBUG, + format='%(levelname)s: %(message)s' + ) + + info('Platform: %s', platform) + + info('Version Info: %s', version_info) + + parser.logLogPath() + + parser.setLexer(argv, description='Handles arbitrary arguments to be executed.') + + parser.setNamespace() + + namespace = parser.getNamespace() + + parser.setCommand() + + clear() + + if namespace['file']: + parser.pipeCall('a') + else: + parser.call() + + parser.logClock() + + if namespace['pause']: + parser.printClock() + parser.pause() + + exit(parser.exitCode()) diff --git a/keymaps/atom-python-run.json b/keymaps/atom-python-run.json index 06ffd68..ac9ab33 100644 --- a/keymaps/atom-python-run.json +++ b/keymaps/atom-python-run.json @@ -1,6 +1,6 @@ { "atom-text-editor": { - "f5": "Python run: run", - "f6": "Python run: run" + "f5": "Python run: run-f5", + "f6": "Python run: run-f6" } } diff --git a/lib/atom-python-run.js b/lib/atom-python-run.js index 9badc0f..17b4c47 100644 --- a/lib/atom-python-run.js +++ b/lib/atom-python-run.js @@ -1,33 +1,204 @@ -"use strict"; - -var child_process = require("child_process"); -var path = require("path"); -var process = require("process"); +'use strict'; module.exports = { - activate: () => { - atom.commands.add("atom-text-editor", "Python run: run", run); - } -}; + activate() { + const CompositeDisposable = require('atom').CompositeDisposable; + const path = require('path'); + const terminal = require('./terminal'); + + this.subscriptions = new CompositeDisposable(); + + let disposables = [ + atom.commands.add('atom-text-editor', 'Python run: run-f5', saveAndRun.bind(this, 'f5')), + atom.commands.add('atom-text-editor', 'Python run: run-f6', saveAndRun.bind(this, 'f6')), + ]; + + function saveAndRun(key) { + try { + console.log(`${key} pressed`); + // save() returns a promise + let save = atom.workspace.getActiveTextEditor().save(); + if (save) { + // Atom expects that promise to be resolved or rejected + return save.then(() => { + run({ + 'command': atom.config.get(`atom-python-run.${key}Command`), + 'pause': atom.config.get(`atom-python-run.${key}Pause`), + }); + }); + } else { // editor.save() will be sync and returns null on atom v1.18 and older + run({ + 'command': atom.config.get(`atom-python-run.${key}Command`), + 'pause': atom.config.get(`atom-python-run.${key}Pause`), + }); + } + } catch(error) { + // the promise failed and the exception was caught + console.log(`atom-python-run: saveAndRun: ${error}`); + atom.notifications.addError(`atom-python-run: \`${error}\`.`); + // nothing to do, so we return undefined here + return; + } + } + + function run(config) { + let shell, spawn, tty; + let editor = atom.workspace.getActiveTextEditor(); + let info = path.parse(editor.buffer.file.path); + let args = config.command.split(' '); + + // load configuration settings in to 'config' object + config = Object.assign(config, { + 'terminal': atom.config.get('atom-python-run.terminal'), + 'pipeFile': atom.config.get('atom-python-run.pipeFile'), + 'pipePath': atom.config.get('atom-python-run.pipePath'), + 'extensions': atom.config.get('atom-python-run.extensionFilter'), + 'consoleLog': atom.config.get('atom-python-run.consoleLog') + }); + + function format(string, object) { + return string.replace(/{.*?}/g, (element) => + object[element.substring(1, element.length - 1)] + ); + } + + args.forEach((item, index, arr) => { + arr[index] = format(item, { + 'file': editor.buffer.file.path, + 'dir': info.dir, + 'name': info.name, + 'ext': info.ext, + }); + }); -function run() { - var editor = atom.workspace.getActiveTextEditor(); - if (!editor) { - return; - } - var file = atom.workspace.getActiveTextEditor().buffer.file.path; - var info = path.parse(file); - atom.notifications.add("info", "Saving " + info.base + " ..."); - editor.save(); - if (info.ext != ".py") { - atom.notifications.add("warning", info.base + " is not a .py file, exit."); - return; - } - atom.notifications.add("info", "Running " + info.base + " ..."); - var child = child_process.spawn("cmd", [ - "/c", "start", __dirname + "/../bin/cp.exe", "python.exe", "\"" + file + "\"" - ], { - detach: true - }); - child.unref(); -} + if (2 > args.length) { + atom.notifications.addError(`atom-python-run: \`FnKeyError\`: Invalid argument length for \`Fn-Key Command\`: requires \`repl [options] {file}\` formatting. Got \`${config.command}\` instead.`); + return; + } + + if (config.extensions.length && !config.extensions.includes(info.ext)) { + atom.notifications.addError(`atom-python-run: \`ExtensionsError\`: \`extensions\` option has been enabled, but only \`${config.extensions}\` are allowed. Cowardly refusing to continue...`); + return; + } + + if (config.pipeFile && ('' === config.pipePath || null == config.pipePath)) { + atom.notifications.addError(`atom-python-run: \`PipeError\`: \`pipe\` option has been enabled, but has no \`file\` to write to. Cowardly refusing to continue...`); + return; + } + + if ('win32' === process.platform) { + shell = new terminal.Shell(__dirname + '\\..\\cp\\main.py'); + } else { + shell = new terminal.Shell(__dirname + '/../cp/main.py'); + } + + spawn = new terminal.SpawnWrapper(shell.object); + + // set up a user defined 'shell' + if (2 <= config.terminal.length) { + // requires a 'shell' name and at least one 'option' + if (config.terminal[0] != null && config.terminal[1] != null) { + spawn.object.shell = config.terminal[0]; + // more than one option may be required depending on the system + spawn.object.option = []; + for (let option of config.terminal) { + if (option === config.terminal[0]) { + continue; + } + spawn.object.option.push(option); + } + } + } + + tty = spawn.tty({ + 'pause': config.pause, + 'pipeFile': config.pipeFile, + 'pipePath': config.pipePath, + 'log': config.consoleLog, + 'args': args, + 'options': { + cwd: info.dir, + detached: true + } + }); + + try { + tty.on('error', function (error) { + atom.notifications.addError(`atom-python-run: \`TerminalError\`: There was a problem opening \`${spawn.object.shell}\`. If your computer doesn't have \`${spawn.object.shell}\` installed, you can set your own terminal on the package's Settings.`); + }); + tty.unref(); + } catch(e) { + atom.notifications.addError(`atom-python-run: \`ExceptionError\`: \`terminal\` refused to spawn a \`tty\` instance: \`${e}\``); + return; + } + } + + this.subscriptions.add(...disposables); + }, + deactivate() { + this.subscriptions.dispose(); + }, + config: { + consoleLog: { + title: 'Console Log', + description: 'Output log to `atom`s built-in console (`Slow`)', + type: 'boolean', + default: true + }, + extensionFilter: { + title: 'Extension filter', + description: 'Leave it empty it accepts all files, or you can try `.py`', + type: 'array', + items: { + type: 'string', + }, + default: [] + }, + f5Command: { + title: 'F5 Command', + description: '`{file}` = `{dir}/{name}{ext}`', + type: 'string', + default: 'python {file}' + }, + f6Command: { + title: 'F6 Command', + description: 'Same as above', + type: 'string', + default: 'python {file}' + }, + f5Pause: { + title: 'Pause (F5)', + description: 'Show elapsed time and pause `cp`', + type: 'boolean', + default: true + }, + f6Pause: { + title: 'Pause (F6)', + description: 'Show elapsed time and pause `cp`', + type: 'boolean', + default: true + }, + pipeFile: { + title: 'Pipe to File', + description: 'Pipe `cp`s output to a file', + type: 'boolean', + default: false + }, + pipePath: { + title: 'Pipe to file', + description: 'The `destination` or `file` to write to', + type: 'string', + default: '' + }, + terminal: { + title: 'Terminal', + description: 'Leave it empty to use default `shell`, `option`; or you can try `terminator`, `-x`', + type: 'array', + items: { + type: 'string', + }, + default: [] + } + }, + subscriptions: null +}; diff --git a/lib/terminal.js b/lib/terminal.js new file mode 100644 index 0000000..5f42f98 --- /dev/null +++ b/lib/terminal.js @@ -0,0 +1,355 @@ +'use strict'; + + +// required for SpawnWrapper class +const spawn = require('child_process').spawn; + + +class Base { + constructor(base, ...source){ + this.set(base, ...source) + } + + get() { + return this.object; + } + + set(base, ...source) { + if (null == base) { + this.object = new Object(); + } else if (null == source) { + this.object = Object.assign({}, base); + } else { + this.object = Object.assign(base, ...source); + } + } + + isEmpty() { + for(var property in this.object) { + if(this.object.hasOwnProperty(property)) { + return false; + } + } + return JSON.stringify(this.object) === JSON.stringify({}); + } +} + + +class LinuxSessionWrapper extends Base { + constructor() { + super(); + let shell = this.setupDefaultShell(); + let option = this.setupDefaultOption(shell); + this.set({ + 'shell': shell, + 'option': option + }); + }; + + getSessionTerminal() { + switch (process.env.GDMSESSION) { + case 'ubuntu': + case 'ubuntu-2d': + case 'gnome': + case 'gnome-shell': + case 'gnome-classic': + case 'gnome-fallback': + case 'cinnamon': + return "gnome-terminal"; + case 'mate': + return "mate-terminal"; + case 'xfce': + return "xfce4-terminal"; + case 'kde-plasma': + return "konsole"; + case 'Lubuntu': + return "lxterminal"; + default: + return null; + } + } + + getDesktopTerminal() { + switch (process.env.XDG_CURRENT_DESKTOP) { + case 'Unity': + case 'GNOME': + case 'X-Cinnamon': + return "gnome-terminal"; + case 'XFCE': + return "xfce4-terminal"; + case 'KDE': + return "konsole"; + case 'LXDE': + return "lxterminal"; + case 'MATE': + return "mate-terminal"; + default: + return "xterm"; + } + } + + setupDefaultShell() { + let shell = this.getSessionTerminal(); + + if (null === shell) { + shell = this.getDesktopTerminal(); + } + + return shell; + } + + setupDefaultOption(shell) { + // determine the shells default execution option + switch (shell) { + case 'gnome-terminal': + case 'mate-terminal': + case 'xfce4-terminal': + return "-x"; + case 'konsole': + case 'lxterminal': + default: + return "-e"; + } + } +}; + + +class MetaShell extends Base { + constructor(base, ...source) { + super(); + this.setBase(base, ...source); + this.setPath(base); + this.setCommand(); + } + + setBase(base, ...source) { + this.set({ + 'shell': base.shell, + 'option': base.option, + 'call': base.call + }, ...source); + } + + setPath(base) { + function defaultPath() { + if ('win32' === process.platform) { + return `${process.env.USERPROFILE}\\.atom\\packages\\atom-python-run\\cp\\main.py`; + } + return `${process.env.HOME}/.atom/packages/atom-python-run/cp/main.py`; + } + this.set(this.get(), { + 'script': (function(path) { + if (null == path) { + path = defaultPath(); + } + return path; + })(base.script) + }); + } + + setCommand() { + let object = this.get(); + this.set(object, { + 'command': function (args) { + let path = ''; + for (let token of args) { + path += ` ${token}`; + } + return `tell app "Terminal" to do script "${path}"` + } + }); + } +} + + +class Shell extends Base { + constructor(path) { + super(); + this.set(this.setUp(path)); + } + + win32(path) { + return new MetaShell({ + 'shell': process.env.COMSPEC, + 'option': ['/c', 'start'], + 'call': 'python', + 'script': path + }); + } + + darwin(path) { + return new MetaShell({ + 'shell': 'osascript', + 'option': ['-e'], + 'call': 'python', + 'script': path + }); + } + + linux(path) { + let session = new LinuxSessionWrapper(); + return new MetaShell({ + 'shell': session.get().shell, + 'option': [session.get().option], + 'call': 'python', + 'script': path + }); + } + + setUp(path) { + switch (process.platform) { + case 'win32': + return this.win32(path).object; + case 'darwin': + return this.darwin(path).object; + case 'linux': + return this.linux(path).object; + default: + return null; + } + } +} + + +class Log extends Base { + constructor(base, ...source) { + super(); + this.set(base, ...source) + } + + metaData() { + console.log( + `platform: ${process.platform}\n` + + `shell: ${this.object.meta.shell}\n` + + `option: ${this.object.meta.option}\n` + + `call: ${this.object.meta.call}\n` + + `script: ${this.object.meta.script}\n` + + `command: ${this.object.meta.command(this.get().args)}` + ); + } + + data() { + console.log( + `pause: ${this.object.pause}\n` + + `pipeFile: ${this.object.pipeFile}\n` + + `pipePath: ${this.object.pipePath}\n` + + `log: ${this.object.log}\n` + + `args: ${this.object.args}\n` + + `options: ${JSON.stringify(this.object.options, null, 4)}` + ); + } + + tty() { + this.object.tty.stdout.on('data', (data) => { + console.log(`stdout: ${data}`); + }); + this.object.tty.stderr.on('data', (data) => { + console.log(`stderr: ${data}`); + }); + this.object.tty.on('close', (code) => { + console.log(`child process exited with code ${code}`); + }); + } + + call() { + this.metaData(); + this.data(); + this.tty(); + } +} + + +class SpawnWrapper extends Base { + constructor(base, ...source) { + super(); + this.set(base, ...source); + } + + spawnArgs(model) { + if (model.pause && !model.pipeFile) { + return [this.object.call, this.object.script, + '--pause', '--', ...model.args + ]; + } + if (!model.pause && model.pipeFile) { + return [this.object.call, this.object.script, + '--file', model.pipePath, '--', ...model.args + ]; + } + if (model.pause && model.pipeFile) { + return [this.object.call, this.object.script, + '-p', '-f', model.pipePath, '--', ...model.args + ]; + } + return [this.object.call, this.object.script, '--', ...model.args]; + } + + spawnWin32(model) { + let args = this.spawnArgs(model); + return spawn( + this.object.shell, + [...this.object.option, ...args], + model.options + ) + } + + spawnDarwin(model) { + let args = this.spawnArgs(model); + return spawn( + this.object.shell, + [...this.object.option, this.object.command(args)], + model.options + ); + } + + spawnWrapper(model) { + switch (process.platform) { + case 'linux': + case 'win32': + return this.spawnWin32(model); + case 'darwin': + return this.spawnDarwin(model); + default: + return null; + } + } + + optionWrapper(options) { + function isEmpty(object) { + for(var property in object) { + if(object.hasOwnProperty(property)) { + return false; + } + } + return JSON.stringify(object) === JSON.stringify({}); + } + if (isEmpty(options)) { + options = { + 'cwd': undefined, + 'detached': false + }; + } + return options; + } + + tty(model) { + model.options = this.optionWrapper(model.options); + let tty = this.spawnWrapper(model); + + if (model.log) { + new Log({ + 'tty': tty, + 'meta': this.object, + }, model).call(); + } + return tty; + } +} + + +exports.Base = Base; +exports.LinuxSessionWrapper = LinuxSessionWrapper; +exports.MetaShell = MetaShell; +exports.Shell = Shell; +exports.Log = Log; +exports.SpawnWrapper = SpawnWrapper; diff --git a/lib/test.js b/lib/test.js new file mode 100644 index 0000000..1366d7c --- /dev/null +++ b/lib/test.js @@ -0,0 +1,23 @@ +const terminal = require('./terminal'); + +let shell, spawn; + +shell = new terminal.Shell() + +// console.log(shell.object); + +spawn = new terminal.SpawnWrapper(shell.object); + +// console.log(spawn.object); + +tty = spawn.tty({ + 'pause': true, + 'pipeFile': false, + 'pipePath': '', + 'log': true, + // 'args': ['python', `${process.env.USERPROFILE}\\Dropbox\\Source\\Python\\hello.py`], + 'args': ['python', `${process.env.HOME}/Documents/Python/hello.py`], + 'options': new Object() +}); + +tty.unref(); diff --git a/package.json b/package.json index cddf6a1..606ca15 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom-python-run", "main": "./lib/atom-python-run.js", - "version": "0.5.1", + "version": "0.9.7", "description": "Run a python source file.", "keywords": [ "python" diff --git a/src/cp.cpp b/src/cp.cpp deleted file mode 100644 index 3698f34..0000000 --- a/src/cp.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include -#include -#include - -int main(int argc, char *argv[]) -{ - char cmd[4096]; - strcpy(cmd, "title "); // prepend title - for (int i = 1; i < argc; i++) { - strcat(cmd, argv[i]); - strcat(cmd, " "); - } - system(cmd); // reset title - int t = clock(); - int r = system(cmd + 6); // origin command - t = clock() - t; - printf("\n" - "Process returned %d (0x%X) execution time : %.3f s\n" - /*"Press any key to continue.\n"*/, - r, r, t / 1000.); - system("pause"); - return r; -}