import sublime import sublime_plugin import traceback import codecs import os from datetime import datetime from threading import Thread import json import webbrowser is_sublime_text_3 = int(sublime.version()) >= 3000 if is_sublime_text_3: from .base_command import BaseCommand from .settings import Settings from .progress_notifier import ProgressNotifier from .cross_platform_process import CrossPlatformProcess from .hasher import Hasher from .gulp_version import GulpVersion from .plugins import PluginList, PluginRegistryCall from .caches import ProcessCache, CacheFile from .status_bar import StatusBar from .timeout import set_timeout, defer, defer_sync, async else: from base_command import BaseCommand from settings import Settings from progress_notifier import ProgressNotifier from cross_platform_process import CrossPlatformProcess from hasher import Hasher from gulp_version import GulpVersion from plugins import PluginList, PluginRegistryCall from caches import ProcessCache, CacheFile from status_bar import StatusBar from timeout import set_timeout, defer, defer_sync, async # # Commands # class GulpCommand(BaseCommand): log_file_name = 'sublime-gulp.log' allowed_extensions = [".babel.js", ".js"] def work(self): self.folders = [] self.gulp_files = [] self.list_gulp_files() def list_gulp_files(self): self.append_paths() if not self.check_for_gulpfile: self.gulp_files = self.folders if len(self.gulp_files) > 0: self.choose_file() else: sufix = "on:\n- %s" % "\n- ".join(self.searchable_folders) if len(self.searchable_folders) > 0 else "" if not self.settings.get("recursive_gulpfile_search", False): sufix += '\n\nCheck the recursive_gulpfile_search setting for nested gulpfiles' self.error_message("gulpfile not found %s" % sufix) def append_paths(self): gulpfile_paths = self.settings.get("gulpfile_paths", []) ignored_gulpfile_folders = self.settings.get("ignored_gulpfile_folders", []) if self.settings.get("recursive_gulpfile_search", False): for folder_path in self.searchable_folders: for dir, dirnames, files in os.walk(folder_path): dirnames[:] = [dirname for dirname in dirnames if dirname not in ignored_gulpfile_folders] self.append_to_gulp_files(dir) else: for folder_path in self.searchable_folders: self.append_to_gulp_files(folder_path) for inner_folder in gulpfile_paths: if(os.name == 'nt'): inner_folder = inner_folder.replace("/", "\\") self.append_to_gulp_files(os.path.join(folder_path, inner_folder)) def append_to_gulp_files(self, folder_path): gulpfile_path = self.get_gulpfile_path(folder_path) self.folders.append(folder_path) if os.path.exists(gulpfile_path) and gulpfile_path not in self.gulp_files: self.gulp_files.append(gulpfile_path) def choose_file(self): if len(self.gulp_files) == 1: self.show_tasks_from_gulp_file(0) else: self.show_quick_panel(self.gulp_files, self.show_tasks_from_gulp_file) def show_tasks_from_gulp_file(self, file_index): if file_index > -1: self.working_dir = self.gulp_files[file_index] if self.task_name is not None: self.run_gulp_task() else: defer(self.show_tasks) def show_tasks(self): self.tasks = self.list_tasks() if self.tasks is not None: self.show_quick_panel(self.tasks, self.task_list_callback) def list_tasks(self): try: self.callcount = 0 json_result = self.fetch_json() except TypeError as e: self.error_message("Could not read available tasks.\nMaybe the JSON cache (.sublime-gulp.cache) is malformed?") except Exception as e: print(traceback.format_exc()) self.error_message(str(e)) else: tasks = [[name, self.dependencies_text(task)] for name, task in json_result.items()] return sorted(tasks, key=lambda task: task) def dependencies_text(self, task): return "Dependencies: " + task['dependencies'] if task['dependencies'] else "" def fetch_json(self): cache_file = CacheFile(self.working_dir) gulpfile = self.get_gulpfile_path(self.working_dir) data = None if cache_file.exists(): filesha1 = Hasher.sha1(gulpfile) data = cache_file.read() if gulpfile in data and data[gulpfile]["sha1"] == filesha1: return data[gulpfile]["tasks"] self.callcount += 1 if self.callcount == 1: return self.write_to_cache() if data is None: raise Exception("Could not write to cache gulpfile.") if gulpfile in data: raise Exception("Sha1 from gulp cache ({0}) is not equal to calculated ({1}).\nTry erasing the cache and running Gulp again.".format(data[gulpfile]["sha1"], filesha1)) else: raise Exception("Have you renamed a folder?.\nSometimes Sublime doesn't update the project path, try removing the folder from the project and adding it again.") def write_to_cache(self): process = CrossPlatformProcess(self.working_dir) (stdout, stderr) = process.run_sync(r'node "%s/write_tasks_to_cache.js"' % self.settings.package_path()) if process.failed: try: self.write_to_cache_without_js() except: if process.returncode() == 127: raise Exception("\"node\" command not found.\nPlease be sure to have nodejs installed on your system and in your PATH (more info in the README).") elif stderr: self.log_errors(stderr) raise Exception("There was an error running gulp, make sure gulp is running correctly in your project.\nFor more info check the sublime-gulp.log file") return self.fetch_json() def write_to_cache_without_js(self): process = CrossPlatformProcess(self.working_dir) (stdout, stderr) = process.run_sync(r'gulp -v') if process.failed or not GulpVersion(stdout).supports_tasks_simple(): raise Exception("Gulp: Could not get the current gulp version or your gulp CLI version is lower than 3.7.0") (stdout, stderr) = process.run_sync(r'gulp --tasks-simple') gulpfile = self.get_gulpfile_path(self.working_dir) if not stdout: raise Exception("Gulp: The result of `gulp --tasks-simple` was empty") CacheFile(self.working_dir).write({ gulpfile: { "sha1": Hasher.sha1(gulpfile), "tasks": dict((task, { "name": task, "dependencies": "" }) for task in stdout.split("\n") if task) } }) def get_gulpfile_path(self, base_path): for extension in GulpCommand.allowed_extensions: gulpfile_path = os.path.join(base_path, "gulpfile" + extension) if os.path.exists(gulpfile_path): return os.path.normpath(gulpfile_path) return gulpfile_path def log_errors(self, text): if not self.settings.get("log_errors", True): return log_path = os.path.join(self.working_dir, GulpCommand.log_file_name) header = "Remember that you can report errors and get help in https://github.com/nicosantangelo/sublime-gulp" if not os.path.isfile(log_path) else "" timestamp = str(datetime.now().strftime("%m-%d-%Y %H:%M")) with codecs.open(log_path, "a", "utf-8", errors='replace') as log_file: log_file.write(header + "\n\n" + timestamp + ":\n" + text) def task_list_callback(self, task_index): if task_index > -1: self.task_name = self.tasks[task_index][0] self.task_flag = self.get_flag_from_task_name() self.run_gulp_task() def run_gulp_task(self): task = self.construct_gulp_task() Thread(target=self.run_process, args=(task, )).start() def construct_gulp_task(self): self.show_running_status_in_output_panel() return r"gulp %s %s" % (self.task_name, self.task_flag) def run_process(self, task): process = CrossPlatformProcess(self.working_dir) process.run(task) self.status_bar.update() stdout, stderr = process.communicate(self.append_to_output_view_in_main_thread) defer_sync(lambda: self.finish(stdout, stderr)) def finish(self, stdout, stderr): finish_message = "gulp %s %s finished %s" % (self.task_name or '', self.task_flag, "with some errors." if stderr else "!") self.status_message(finish_message) self.status_bar.update() if not self.silent: self.set_output_close_on_timeout() elif stderr and self.settings.get("show_silent_errors", False): self.silent = False self.show_running_status_in_output_panel() self.append_to_output_view(stdout) self.append_to_output_view(stderr) self.silent = True def show_running_status_in_output_panel(self): with_flag_text = (' with %s' % self.task_flag) if self.task_flag else '' self.show_output_panel("Running '%s'%s...\n" % (self.task_name, with_flag_text)) class GulpArbitraryCommand(GulpCommand): def show_tasks_from_gulp_file(self, file_index): if file_index > -1: self.working_dir = self.gulp_files[file_index] self.show_input_panel(caption="gulp", on_done=self.after_task_input) def after_task_input(self, task_name=None): if task_name: self.task_name = task_name self.task_flag = '' self.run_gulp_task() class GulpLastCommand(BaseCommand): def work(self): if ProcessCache.last_task_name: task_name = ProcessCache.last_task_name self.window.run_command("gulp", { "task_name": task_name }) else: self.status_message("You need to run a task first") class GulpKillTaskCommand(BaseCommand): def work(self): ProcessCache.refresh() if ProcessCache.empty(): self.status_message("There are no running tasks") else: self.procs = ProcessCache.get() quick_panel_list = [[process.last_command, process.working_dir, 'PID: %d' % process.pid] for process in self.procs] self.show_quick_panel(quick_panel_list, self.kill_process, font=0) def kill_process(self, index=-1): if index >= 0 and index < len(self.procs): process = self.procs[index] try: process.kill() except ProcessLookupError as e: print('Process %d seems to be dead already' % process.pid) self.show_output_panel('') self.append_to_output_view("\n%s killed! # %s | PID: %d\n" % process.to_tuple()) class GulpKillCommand(BaseCommand): def work(self): ProcessCache.refresh() if ProcessCache.empty(): self.status_message("There are no running tasks") else: self.append_processes_to_output_view() ProcessCache.kill_all() self.append_to_output_view("\nAll running tasks killed!\n") def append_processes_to_output_view(self): self.show_output_panel("\nFinishing the following running tasks:\n") ProcessCache.each(lambda process: self.append_to_output_view("$ %s # %s | PID: %d\n" % process.to_tuple())) class GulpShowPanelCommand(BaseCommand): def work(self): self.show_panel() class GulpHidePanelCommand(BaseCommand): def work(self): self.close_panel() class GulpPluginsCommand(BaseCommand): def work(self): self.plugins = None self.request_plugin_list() def request_plugin_list(self): progress = ProgressNotifier("%s: Working" % Settings.PACKAGE_NAME) thread = PluginRegistryCall() thread.start() self.handle_thread(thread, progress) def handle_thread(self, thread, progress): if thread.is_alive() and not thread.error: set_timeout(lambda: self.handle_thread(thread, progress), 100) else: progress.stop() if thread.result: plugin_response = json.loads(thread.result.decode('utf-8')) self.plugins = PluginList(plugin_response) self.show_quick_panel(self.plugins.quick_panel_list(), self.open_in_browser, font=0) else: self.error_message(self.error_text_for(thread)) def error_text_for(self, thread): error_tuple = ( "The plugin repository seems to be down.", "If http://gulpjs.com/plugins is working, please report this issue at the Sublime Gulp repo (https://github.com/nicosantangelo/sublime-gulp).", "Thanks!", thread.error ) return "\n\n%s\n\n%s\n\n%s\n\n%s" % error_tuple def open_in_browser(self, index=-1): if index >= 0 and index < self.plugins.length: webbrowser.open_new(self.plugins.get(index).get('homepage')) class GulpDeleteCacheCommand(GulpCommand): def choose_file(self): if len(self.gulp_files) == 1: self.delete_cache(0) else: self.show_quick_panel(self.gulp_files, self.delete_cache) def delete_cache(self, file_index): if file_index > -1: self.working_dir = self.gulp_files[file_index] try: cache_file = CacheFile(self.working_dir) if cache_file.exists(): cache_file.remove() self.status_message('Cache removed successfully') except Exception as e: self.status_message("Could not remove cache: %s" % str(e)) class GulpExitCommand(sublime_plugin.WindowCommand): def run(self): try: self.window.run_command("gulp_kill") finally: self.window.run_command("exit") class GulpUpdateStatusBarCommand(sublime_plugin.TextCommand): def run(self, edit): if self.view.settings().get('is_widget'): return window = self.view.window() if not window or \ (self.view.file_name() and self.view == window.transient_view_in_group(window.active_group())): # it means it is an transient view of a regular file return StatusBar(window).update() def plugin_loaded(): def load_process_cache(): for process in ProcessCache.get_from_storage(): ProcessCache.add( CrossPlatformProcess(process['working_dir'], process['last_command'], process['pid']) ) async(load_process_cache, 200, silent=True) if not is_sublime_text_3: plugin_loaded()