Sublime Text plug-in to creates a Gist from of the current view.

'''
sublime_to_gist.py

author: Dealga McArdle, 2013
functionality: upload anonymous gist from current tab
sublime API docs: sublimetext.com/docs/2/api_reference.html

milestones

[x] print current tab name
[-] print current tab content, if nothing selected
[x] print current selected text
[x] upload anonymously + spawn browser
[x] trim any leading whitespace, if selection indented.
[ ] deal with tabs where there should be spaces
'''

import json
import os.path
import sublime
import sublime_plugin
import sys

from urllib import urlopen


def nothing_selected():
    sublime.status_message('nothing selected, fool')


def get_file_name(view):
    file_path = view.file_name()
    return os.path.basename(file_path)


def detect_newline_char(selection):
    ''''\n (newline) and \r (carriage return). '''
    newline = '\n'
    # carriage = '\r\n'
    # -- find the sublime setting for this.
    # os.linesep  ?
    # supposedly \n is now universal for python..
    return newline


def find_minimal_indent(lines):
    """for larger files this can be done more efficiently"""
    white_space_set = set([])

    for line in lines:
        lstripped = line.lstrip()
        num_whitespaces = len(line) - len(lstripped)

        if len(line) > 0:
            if num_whitespaces == 0:
                return 0
            white_space_set.update([num_whitespaces])

    # can safely indent by this amount, if zero do nothing.
    return min(white_space_set)


def undent(view, selection):
    '''remove excess whitespace but preserve relative indentation
    --- assumes tabs are not used for space.'''

    # print(repr(view.line_endings()))
    newline_char = detect_newline_char(selection)
    lines = selection.split(newline_char)

    amount_to_indent = find_minimal_indent(lines)

    # no indent required
    if amount_to_indent == 0:
        return selection

    # carve it up!
    undented_lines = []
    for line in lines:
        this_line = ''
        if not line.isspace():
            this_line = line[amount_to_indent:]
        undented_lines.append(this_line)

    # notification to user
    indent_message = 'undented by ' + str(amount_to_indent)
    sublime.status_message(indent_message)

    return newline_char.join(undented_lines)


def main_upload_function(gist_filename, gist_description, gist_body):

    gist_post_data = {'description': gist_description,
                      'public': True,
                      'files': {gist_filename: {'content': gist_body}}}

    json_post_data = json.dumps(gist_post_data).encode('utf-8')

    def get_gist_url(found_json):
        wfile = json.JSONDecoder()
        wjson = wfile.decode(found_json)
        gist_url = 'https://gist.github.com/' + wjson['id']

        import webbrowser
        print(gist_url)
        webbrowser.open(gist_url)
        # or just copy url to clipboard?

    def upload_gist():
        print('sending')
        url = 'https://api.github.com/gists'
        json_to_parse = urlopen(url, data=json_post_data)

        print('received response from server')
        # found_json = json_to_parse.readall().decode()   py3
        found_json = json_to_parse.read().decode()
        get_gist_url(found_json)

    upload_gist()


class SublimeToGist(sublime_plugin.TextCommand):

    def run(self, edit):
        if not self.enabled():
            nothing_selected()
            return

        view = self.view
        sel = view.sel()[0]

        file_name = get_file_name(view)
        print(file_name)

        # adjust the sel.begin to be at line start
        sel = view.line(sel)

        # get the content of the selected region
        selection = view.substr(sel)

        # if region is indented, this undents, else returns original
        undented = undent(view, selection)

        main_upload_function(file_name, 'test', undented)
        # sublime.status_message('magnets, bitches')

    def enabled(self):
        '''only allow 1 selection for version 0.1'''

        sels = self.view.sel()    # lists regions,
        nsels = len(sels)          # dir(sels[0]) for methods
        fsel = sels[0]            # first selection

        if nsels == 1 and not fsel.empty():
            return True