Search code examples
sublimetext3shebangbuild-system

How to create Sublime Text 3 build system which reads shebang


How can I create a build system in Sublime Text 3 where "cmd" is replaced with a shebang if it exists?

More specifically, is there a way to alter the Python build system to use the version of Python specified in the shebang, and use a default if no shebang is present?


Solution

  • Sublime build systems have an option named target which specifies a WindowCommand that is to be invoked to perform the build. By default this is the internal exec command. You can create your own command that would examine the file for a shebang and use that interpreter or some default otherwise.

    For example (caveat: I'm not super proficient in Python so this is probably quite ugly):

    import sublime, sublime_plugin
    
    class ShebangerCommand(sublime_plugin.WindowCommand):
        def parseShebang (self, filename):
            with open(filename, 'r') as handle:
                shebang = handle.readline ().strip ().split (' ', 1)[0]
            if shebang.startswith ("#!"):
                return shebang[2:]
            return None
    
        def createExecDict(self, sourceDict):
            current_file = self.window.active_view ().file_name()
            args = dict (sourceDict)
    
            interpreter = args.pop ("interpreter_default", "python")
            exec_args = args.pop ("interpreter_args", ["-u"])
            shebang = self.parseShebang (current_file)
    
            args["shell_cmd"] = "{} {} \"{}\"".format (shebang or interpreter,
                                                       " ".join (exec_args),
                                                       current_file)
    
            return args
    
        def run(self, **kwargs):
            self.window.run_command ("exec", self.createExecDict (kwargs))
    

    You would save this in Packages/User as a python file (e.g. shebanger.py).

    This creates a new command named shebanger that collects the arguments it's been given, examines the file in the currently active view of the window the build is triggered in to see if the first line is a shebang, and then synthesizes the arguments needed for the exec command and runs it.

    Since the default python build system assumes it is building the current file and passing -u as an argument, that's what this command replicates as well. Note however that this code is not 100% correct because any arguments in the shebang line will be ignored, but you get the general idea.

    In use, you would modify the default Python.sublime-build file to look like this:

    {
        // WindowCommand to execute for this build
        "target": "shebanger",
    
        // Use this when there is no shebang
        "interpreter_default": "python",
    
        // Args to pass to the interpreter
        "interpreter_args": ["-u"],
    
        "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
        "selector": "source.python",
    
        "env": {"PYTHONIOENCODING": "utf-8"},
    
        "variants":
        [
            {
                "name": "Syntax Check",
                "interpreter_args": ["-m py_compile"],
            }
        ]
    }
    

    Notice that in the variant we override what the interpreter arguments are; you could also override the default interpreter there as well if desired.