import sublime import tempfile import re import os import subprocess from .settings import r_box_settings ANSI_ESCAPE = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]') class ScriptMixin: message_shown = False def find_working_dir(self): if hasattr(self, "window"): view = self.window.active_view() elif hasattr(self, "view"): view = self.view else: view = None if view and view.file_name(): file_dir = os.path.dirname(view.file_name()) if os.path.isdir(file_dir): return file_dir window = view.window() if view else None if window: folders = window.folders() if folders and os.path.isdir(folders[0]): return folders[0] return None def custom_env(self): paths = r_box_settings.additional_paths() if sublime.platform() == "osx": paths += ["/Library/TeX/texbin", "/usr/local/bin"] env = os.environ.copy() if paths: sep = ";" if sublime.platform() == "windows" else ":" env["PATH"] = env["PATH"] + sep + sep.join(paths) return env def rscript(self, script=None, file=None, args=None, stdin_text=None): cmd = [r_box_settings.rscript_binary()] if script: cmd = cmd + ["-e", script] elif file: cmd = cmd + [file] if args: cmd = cmd + args if sublime.platform() == "windows": # make sure console does not come up startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW else: startupinfo = None working_dir = self.find_working_dir() custom_env = self.custom_env() try: p = subprocess.Popen( cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=working_dir, env=custom_env, startupinfo=startupinfo, universal_newlines=True) stdout, stderr = p.communicate(input=stdin_text) if p.returncode == 0: return ANSI_ESCAPE.sub('', stdout) else: raise Exception( "Failed to execute RScript with the following output:\n\n{}".format(stderr)) except FileNotFoundError as e: if not self.message_shown: sublime.message_dialog( "Rscript binary cannot be found automatically. " "The path to `Rscript` can be specified in the R-Box settings.") self.message_shown = True raise Exception("Rscript binary not found.") def installed_packages(self): return self.rscript("cat(rownames(installed.packages()))").strip().split(" ") def list_package_objects(self, pkg, exported_only=True): if exported_only: objects = self.rscript("cat(getNamespaceExports(asNamespace('{}')))".format(pkg)) else: objects = self.rscript("cat(objects(asNamespace('{}')))".format(pkg)) return objects.strip().split(" ") def get_function_call(self, pkg, funct): out = self.rscript("args({}:::{})".format(pkg, funct)) out = re.sub(r"^function ", funct, out).strip() out = re.sub(r"<bytecode: [^>]+>", "", out).strip() out = re.sub(r"NULL(?:\n|\s)*$", "", out).strip() return out def list_function_args(self, pkg, funct): out = self.rscript("cat(names(formals({}:::{})))".format(pkg, funct)) return out.strip().split(" ") def format_code(self, code, indent=4, width_cutoff=100): formatted_code = self.rscript( "formatR::tidy_source(file('stdin'), indent={:d}, width.cutoff={:d})".format( indent, width_cutoff), stdin_text=code) return formatted_code[0:-1] def detect_free_vars(self, code): dfv_path = tempfile.mkstemp(suffix=".R")[1] data = sublime.load_resource("Packages/R-Box/box/detect_free_vars.R") with open(dfv_path, 'w') as f: f.write(data.replace("\r\n", "\n")) f.close() result = self.rscript( file=dfv_path, stdin_text=code ).strip() try: os.unlink(dfv_path) except Exception: pass return [s.strip() for s in result.split("\n")] if result else []