├── Commands ├── __init__.py ├── create_connection.py ├── create_diff_scratch.py ├── enter_credentials.py ├── load_all_snow_record.py ├── load_modified_records.py ├── load_snow_record.py ├── load_snow_table.py ├── open_service_now_record.py ├── refresh_snow_record.py └── test_connection.py ├── Common ├── __init__.py ├── rest_client.py └── utils.py ├── Context.sublime-menu ├── Events ├── __init__.py └── save_listener.py ├── LICENSE ├── Main.sublime-menu ├── README.md ├── Reloader ├── __init__.py └── module_loader.py ├── ServiceNowSync.py ├── Side Bar.sublime-menu └── __init__.py /Commands/__init__.py: -------------------------------------------------------------------------------- 1 | # import sys 2 | # import imp 3 | 4 | # from .create_connection import * 5 | # from .create_diff_scratch import * 6 | # from .enter_credentials import * 7 | # from .load_all_snow_record import * 8 | # from .load_snow_record import * 9 | # from .load_snow_table import * 10 | # from .open_service_now_record import * 11 | # from .refresh_snow_record import * 12 | # from .test_connection import * 13 | 14 | # imp.reload('servicenow_sync.commands.test_connection') 15 | 16 | # print("different") -------------------------------------------------------------------------------- /Commands/create_connection.py: -------------------------------------------------------------------------------- 1 | from Common.utils import * 2 | import sublime 3 | import sublime_plugin 4 | 5 | class CreateConnectionCommand(sublime_plugin.WindowCommand): 6 | settings = dict() 7 | user = "" 8 | dir = "" 9 | 10 | def run(self): 11 | if not self.window.folders(): 12 | sublime.message_dialog("SNOW Sync Sublime plugin requires an open folder.") 13 | return 14 | else: 15 | self.dir = self.window.folders()[0] 16 | self.window.show_input_panel("Instance Name:", "", self.create_instance, None, None) 17 | 18 | return 19 | 20 | def create_instance(self, instance): 21 | self.settings['instance'] = instance 22 | 23 | self.window.show_input_panel("User Name:", "", self.create_user, None, None) 24 | 25 | def create_user(self, user): 26 | self.user = user 27 | self.window.show_input_panel("Password:", "", self.create_pass, None, None) 28 | 29 | def create_pass(self, password): 30 | cred = self.user + ":" + password 31 | encoded_cred = base64.encodestring(bytes(cred, "utf-8")) 32 | 33 | self.settings['auth'] = "Basic " + encoded_cred.decode("utf-8").replace("\n", "") 34 | save_settings(self.dir, self.settings) 35 | 36 | def is_visible(self): 37 | return is_sn(self.window.folders()) is False -------------------------------------------------------------------------------- /Commands/create_diff_scratch.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | 4 | class CreateDiffScratchCommand(sublime_plugin.TextCommand): 5 | def run(self, edit, content): 6 | self.view.insert(edit, 0, content) -------------------------------------------------------------------------------- /Commands/enter_credentials.py: -------------------------------------------------------------------------------- 1 | from Common.utils import * 2 | import sublime 3 | import sublime_plugin 4 | 5 | class EnterCredentialsCommand(sublime_plugin.WindowCommand): 6 | settings = dict() 7 | user = "" 8 | dir = "" 9 | 10 | def run(self): 11 | self.dir = self.window.folders()[0] 12 | self.window.show_input_panel("User Name:", "", self.create_user, None, None) 13 | 14 | def create_user(self, user): 15 | self.user = user 16 | self.window.show_input_panel("Password:", "", self.create_pass, None, None) 17 | 18 | def create_pass(self, password): 19 | cred = self.user + ":" + password 20 | encoded_cred = base64.encodestring(bytes(cred, "utf-8")) 21 | save_setting(self.dir, "auth", "Basic " + encoded_cred.decode("utf-8").replace("\n", "")) 22 | 23 | def is_visible(self): 24 | return is_sn(self.window.folders()) is True -------------------------------------------------------------------------------- /Commands/load_all_snow_record.py: -------------------------------------------------------------------------------- 1 | from Common.utils import * 2 | import sublime 3 | import sublime_plugin 4 | 5 | class LoadAllSnowRecordCommand(sublime_plugin.WindowCommand): 6 | this_dirs = [] 7 | 8 | def run(self, dirs): 9 | self.this_dirs = dirs 10 | self.window.show_input_panel("Enter an encoded query (or leave blank):", "", self.get_records, None, None) 11 | return 12 | 13 | 14 | def get_records(self, query): 15 | working_dir = self.window.folders()[0] 16 | settings = load_settings(working_dir) 17 | file_dir = self.this_dirs[0] 18 | table_settings = load_settings( file_dir ) 19 | 20 | load_multiple(settings, table_settings, file_dir, query) 21 | return 22 | 23 | 24 | def is_visible(self, dirs): 25 | return is_sn(self.window.folders()) and len(dirs) > 0 -------------------------------------------------------------------------------- /Commands/load_modified_records.py: -------------------------------------------------------------------------------- 1 | from Common.utils import * 2 | import sublime 3 | import sublime_plugin 4 | 5 | class LoadModifiedRecordCommand(sublime_plugin.WindowCommand): 6 | this_dirs = [] 7 | 8 | def run(self, dirs): 9 | working_dir = self.window.folders()[0] 10 | settings = load_settings(working_dir) 11 | file_dir = dirs[0] 12 | table_settings = load_settings(file_dir) 13 | query = "sys_customer_update=true" 14 | 15 | settings = load_settings(working_dir) 16 | table_settings = load_settings( file_dir ) 17 | 18 | load_multiple(settings, table_settings, file_dir, query) 19 | 20 | def is_visible(self, dirs): 21 | return is_sn(self.window.folders()) and len(dirs) > 0 -------------------------------------------------------------------------------- /Commands/load_snow_record.py: -------------------------------------------------------------------------------- 1 | from Common.utils import * 2 | import sublime 3 | import sublime_plugin 4 | 5 | class LoadSnowRecordCommand(sublime_plugin.WindowCommand): 6 | items = [] 7 | table_settings = {} 8 | working_dir = '' 9 | file_dir = "" 10 | 11 | def run(self, dirs): 12 | self.working_dir = self.window.folders()[0] 13 | settings = load_settings(self.working_dir) 14 | self.file_dir = dirs[0] 15 | self.table_settings = load_settings(self.file_dir) 16 | 17 | query = "" 18 | 19 | if 'query' in self.table_settings: 20 | query = self.table_settings['query'] 21 | 22 | if settings is not False and self.table_settings is not False: 23 | fields = ['sys_id','sys_updated_on'] 24 | 25 | if "display" in self.table_settings: 26 | fields.append(self.table_settings["display"]) 27 | 28 | fields = ",".join(fields); 29 | self.items = get_list(settings, self.table_settings['table'], query, fields) 30 | item_list = [] 31 | 32 | for item in self.items: 33 | item_list.append([item[self.table_settings['display']], item['sys_id']]) 34 | 35 | self.window.show_quick_panel(item_list, self.on_done) 36 | 37 | def on_done(self, index): 38 | if index != -1: 39 | item = self.items[index] 40 | settings = load_settings(self.working_dir) 41 | 42 | if settings is not False and self.table_settings is not False: 43 | record = get_record(settings,self.table_settings['table'],item['sys_id']) 44 | 45 | if 'grouped' in self.table_settings: 46 | name_field = self.table_settings['display'] 47 | name = record[name_field] 48 | name = re.sub('[^-a-zA-Z0-9_.() ]+', '', name) 49 | grouped_dir = os.path.join(self.file_dir, name) 50 | 51 | if os.path.exists(grouped_dir): 52 | grouped_dir = grouped_dir + "_" + record['sys_id'] 53 | 54 | os.makedirs(grouped_dir) 55 | settings = json.loads('{}') 56 | settings['grouped_child'] = True 57 | settings['id'] = record['sys_id'] 58 | save_settings(grouped_dir, settings) 59 | 60 | for child in self.table_settings['fields']: 61 | body_field = child['field']; 62 | extension = child['extension'] 63 | name = convert_file_name(child['name'] + "." + extension) 64 | file_name = os.path.join(grouped_dir , name) 65 | 66 | if os.path.exists(file_name): 67 | if sublime.ok_cancel_dialog("File already exists.\nOverwrite?")==False: 68 | return False 69 | 70 | doc = record[body_field] 71 | write_doc_file(file_name, doc) 72 | 73 | if 'multi' in self.table_settings: 74 | for child in os.listdir(self.file_dir): 75 | test_path = os.path.join(self.file_dir, child) 76 | if os.path.isdir(test_path): 77 | sub_settings = load_settings(test_path) 78 | body_field = child 79 | name_field = sub_settings['display'] 80 | extension = sub_settings['extension'] 81 | name = convert_file_name(record[name_field] + "." + extension) 82 | 83 | doc = record[body_field] 84 | file_name = os.path.join(self.file_dir, child, name) 85 | 86 | if os.path.exists(file_name): 87 | if sublime.ok_cancel_dialog("File already exists.\nOverwrite?") is False: 88 | return False 89 | 90 | write_doc_file(file_name, doc) 91 | 92 | add_file(test_path, record['sys_id'], name) 93 | 94 | else: 95 | body_field = self.table_settings['body_field'] 96 | name_field = self.table_settings['display'] 97 | extension = self.table_settings['extension'] 98 | name = convert_file_name(record[name_field] + "." + extension) 99 | 100 | doc = record[body_field] 101 | file_name = os.path.join(self.file_dir, name) 102 | 103 | if os.path.exists(file_name): 104 | if sublime.ok_cancel_dialog("File already exists.\nOverwrite?") is False: 105 | return False 106 | 107 | write_doc_file(file_name, doc) 108 | 109 | add_file(self.file_dir, record['sys_id'], name) 110 | 111 | self.window.open_file(os.path.join(self.file_dir, name)) 112 | 113 | return 114 | 115 | def is_visible(self, dirs): 116 | return is_sn(self.window.folders()) and len(dirs) > 0 -------------------------------------------------------------------------------- /Commands/load_snow_table.py: -------------------------------------------------------------------------------- 1 | from Common.utils import * 2 | import sublime 3 | import sublime_plugin 4 | 5 | class LoadSnowTableCommand(sublime_plugin.WindowCommand): 6 | items = [] 7 | folder = '' 8 | display = '' 9 | description = '' 10 | body_field = '' 11 | extension = '' 12 | settings = {} 13 | file_dir = "" 14 | item = {} 15 | table_fields = {} 16 | 17 | def run(self): 18 | working_dir = self.window.folders() 19 | app_settings = load_settings(working_dir[0]) 20 | print(app_settings) 21 | self.file_dir = working_dir[0] 22 | 23 | if app_settings is not False: 24 | self.items = get_tables(app_settings) 25 | item_list = [] 26 | 27 | for item in self.items: 28 | item_list.append([item['label'], item['name']]) 29 | 30 | self.window.show_quick_panel(item_list, self.sync_table) 31 | 32 | def sync_table(self, index): 33 | if index != -1: 34 | self.item = self.items[index] 35 | name = self.item['name'] 36 | 37 | self.table_fields = json.loads(table_field_list) 38 | 39 | if name in self.table_fields: 40 | if len(self.table_fields[name]) == 1: 41 | self.create_single() 42 | else: 43 | self.create_multi() 44 | else: 45 | self.custom_get_folder_name() 46 | 47 | return 48 | 49 | def create_single(self): 50 | name = self.item['name'] 51 | parent_settings = {} 52 | 53 | friendly_name = str.replace(str.lower(self.item['label']), " ", "_") + "s" 54 | os.makedirs(os.path.join(self.file_dir, friendly_name)) 55 | 56 | parent_settings['table'] = self.item['name'] 57 | parent_settings['display'] = 'name' 58 | parent_settings['body_field'] = self.table_fields[name][0]['field'] 59 | parent_settings['extension'] = self.table_fields[name][0]['extension'] 60 | parent_settings['files'] = {} 61 | 62 | save_settings(os.path.join(self.file_dir, friendly_name), parent_settings) 63 | 64 | def create_multi(self): 65 | name = self.item['name'] 66 | parent_settings = {} 67 | 68 | friendly_name = str.replace(str.lower(self.item['label']), " ", "_") + "s" 69 | base_folder = os.path.join(self.file_dir, friendly_name) 70 | os.makedirs(base_folder) 71 | 72 | parent_settings['table'] = self.item['name'] 73 | parent_settings['display'] = 'name' 74 | parent_settings['multi'] = True 75 | 76 | save_settings(base_folder, parent_settings) 77 | 78 | for field in self.table_fields[name]: 79 | sub_settings = {} 80 | folder_name = field['field'] 81 | sub_folder = os.path.join(base_folder, folder_name) 82 | os.makedirs(sub_folder) 83 | 84 | sub_settings['table'] = self.item['name'] 85 | sub_settings['display'] = 'name' 86 | sub_settings['body_field'] = field['field'] 87 | sub_settings['extension'] = field['extension'] 88 | sub_settings['files'] = {} 89 | sub_settings['sub'] = 'true' 90 | 91 | save_settings(sub_folder, sub_settings) 92 | 93 | def custom_get_folder_name(self): 94 | friendly_name = str.replace(str.lower(self.item['label']), " ", "_") 95 | self.window.show_input_panel("Table Folder Name:", friendly_name + "s", self.custom_create_folder, None, None) 96 | 97 | return 98 | 99 | def custom_create_folder(self, name): 100 | self.file_dir = os.path.join(self.file_dir, name) 101 | 102 | os.makedirs(self.file_dir) 103 | 104 | self.settings['table'] = self.item['name'] 105 | self.window.show_input_panel("Display Field:", "name", self.custom_get_display_field, None, None) 106 | 107 | def custom_get_display_field(self, display): 108 | self.settings['display'] = display 109 | 110 | self.window.show_input_panel("Body Field:", "script", self.custom_get_extension, None, None) 111 | return 112 | 113 | def custom_get_extension(self, body_field): 114 | self.settings['body_field'] = body_field 115 | 116 | self.window.show_input_panel("File Extension:", "js", self.custom_create_settings, None, None) 117 | return 118 | 119 | def custom_create_settings(self, extension): 120 | self.settings['extension'] = extension 121 | self.settings['files'] = {} 122 | 123 | file_name = os.path.join(self.file_dir, "service-now.json") 124 | f = open(file_name, 'wb') 125 | f.write(bytes(json.dumps(self.settings, indent=4), 'utf-8')) 126 | f.close() 127 | 128 | return 129 | 130 | def is_visible(self): 131 | return is_sn(self.window.folders()) -------------------------------------------------------------------------------- /Commands/open_service_now_record.py: -------------------------------------------------------------------------------- 1 | from Common.utils import * 2 | import sublime 3 | import sublime_plugin 4 | import webbrowser 5 | 6 | class OpenSnowRecordCommand(sublime_plugin.TextCommand): 7 | def run(self, edit): 8 | if self.view.file_name(): 9 | working_dir = self.view.window().folders()[0] 10 | settings = load_settings(working_dir) 11 | 12 | full_file_name = self.view.file_name() 13 | file_path = get_file_path(full_file_name) 14 | file_name = get_file_name(full_file_name) 15 | 16 | if file_name == "service-now.json": 17 | return False 18 | 19 | table_settings = load_settings(file_path) 20 | 21 | if "id" in table_settings: 22 | parent_path = file_path[::-1] 23 | parent_path = parent_path.split('/',1)[1] 24 | parent_path = parent_path[::-1] 25 | parent_settings = load_settings(parent_path) 26 | table = parent_settings["table"] 27 | sys_id = table_settings["id"] 28 | else: 29 | table = table_settings['table'] 30 | sys_id = lookup_record_id(file_name, table_settings) 31 | 32 | if sys_id is not False: 33 | url = "https://" + settings['instance'] + ".service-now.com/" 34 | url = url + table + ".do?sys_id=" + sys_id 35 | webbrowser.open_new_tab(url) 36 | 37 | def is_visible(self): 38 | return is_sn(self.view.window().folders()) -------------------------------------------------------------------------------- /Commands/refresh_snow_record.py: -------------------------------------------------------------------------------- 1 | from Common.utils import * 2 | import sublime 3 | import sublime_plugin 4 | 5 | class RefreshSnowRecordCommand(sublime_plugin.WindowCommand): 6 | items = [] 7 | table_settings = {} 8 | 9 | def run(self, files): 10 | working_dir = self.window.folders()[0] 11 | 12 | settings = load_settings(working_dir) 13 | full_file_name = files[0] 14 | file_path = get_file_path(full_file_name) 15 | file_name = get_file_name(full_file_name) 16 | file_type = get_file_type(full_file_name) 17 | 18 | if file_name == "service-now.json" or file_type == "spec": 19 | return False 20 | 21 | table_settings = load_settings(file_path) 22 | 23 | if table_settings is False: 24 | return False 25 | 26 | sys_id = lookup_record_id(file_name, table_settings) 27 | 28 | if sys_id is not False: 29 | result = get_record(settings, table_settings['table'], sys_id) 30 | 31 | if result is not False: 32 | item = result 33 | 34 | body_field = table_settings['body_field'] 35 | name = full_file_name 36 | 37 | doc = item[body_field] 38 | 39 | if os.path.exists(full_file_name): 40 | diffs = diff_file_to_doc(full_file_name, doc) 41 | 42 | if not diffs: 43 | sublime.message_dialog('File is up to date.') 44 | else: 45 | action = sublime.yes_no_cancel_dialog("Server record is newer than local copy.", 46 | "Overwrite Local", "View Differences") 47 | 48 | if action == sublime.DIALOG_YES: 49 | write_doc_file(full_file_name, doc) 50 | 51 | if action == sublime.DIALOG_NO: 52 | diffs = map(lambda line: (line and line[-1] == "\n") and line or line + "\n", diffs) 53 | diffs = ''.join(diffs) 54 | scratch = self.window.new_file() 55 | scratch.set_scratch(True) 56 | scratch.run_command('create_diff_scratch', {'content': diffs}) 57 | 58 | else: 59 | return False 60 | 61 | def is_visible(self, files): 62 | return is_sn(self.window.folders()) and len(files) == 1 -------------------------------------------------------------------------------- /Commands/test_connection.py: -------------------------------------------------------------------------------- 1 | from Common.utils import * 2 | import sublime 3 | import sublime_plugin 4 | 5 | class TestConnectionCommand(sublime_plugin.WindowCommand): 6 | def run(self): 7 | working_dir = self.window.folders()[0] 8 | settings = load_settings(working_dir) 9 | 10 | if settings is not False: 11 | response = get_list(settings, "sys_properties", "") 12 | if response is not False: 13 | sublime.message_dialog("Connection Successful.") 14 | else: 15 | sublime.message_dialog("Connection Unsuccessful.") 16 | 17 | return 18 | 19 | def is_visible(self): 20 | return is_sn(self.window.folders()) -------------------------------------------------------------------------------- /Common/__init__.py: -------------------------------------------------------------------------------- 1 | # from .utils import * 2 | # from .rest_client import * 3 | -------------------------------------------------------------------------------- /Common/rest_client.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import urllib.parse 4 | import urllib.request 5 | import urllib.error 6 | import re 7 | import base64 8 | import json 9 | import os 10 | import webbrowser 11 | import difflib 12 | import codecs 13 | from datetime import datetime 14 | import time 15 | 16 | 17 | # noinspection PyUnresolvedReferences 18 | def http_call(request): 19 | err = "" 20 | timeout = 5 21 | result = "" 22 | 23 | try: 24 | http_file = urllib.request.urlopen(request, timeout=timeout) 25 | result = http_file.read() 26 | except urllib.error.HTTPError as e: 27 | err = 'Error %s' % (str(e.code)) 28 | except urllib.error.URLError as e: 29 | err = 'Error %s' % (str(e.code)) 30 | 31 | if err!="": 32 | print(err) 33 | return False 34 | else: 35 | return result.decode() 36 | 37 | 38 | def http_put(settings, url, data): 39 | json_data = json.dumps(data).encode('utf8'); 40 | request = urllib.request.Request(url, json_data, method="PUT") 41 | request.add_header("Authorization", settings['auth']) 42 | request.add_header("Accept", "application/json") 43 | request.add_header("Content-type", "application/json") 44 | 45 | return http_call(request); 46 | 47 | def http_post(settings, url,data): 48 | json_data = json.dumps(data).encode('utf8'); 49 | request = urllib.request.Request(url, json_data, method="POST") 50 | 51 | request.add_header("Authorization", settings['auth']) 52 | request.add_header("Accept", "application/json") 53 | request.add_header("Content-type", "application/json") 54 | 55 | return http_call(request); 56 | 57 | def http_get(settings, url): 58 | request = urllib.request.Request(url) 59 | 60 | request.add_header("Authorization", settings['auth']) 61 | request.add_header("Accept", "application/json") 62 | request.add_header("Content-type", "application/json") 63 | 64 | return http_call(request); 65 | 66 | def update_record(settings, data, table, sys_id): 67 | url = "https://" + settings['instance'] + ".service-now.com/api/now/table/" + table + '/' + sys_id 68 | result_body = http_put(settings, url, data) 69 | result = json.loads(result_body)['result'] 70 | return result 71 | 72 | 73 | def get_record(settings, table, sys_id): 74 | url = "https://" + settings['instance'] + ".service-now.com/api/now/table/" + table + '/' + sys_id 75 | result_body = http_get(settings, url) 76 | result = json.loads(result_body)['result'] 77 | return result 78 | 79 | 80 | def create_record(settings, data, table): 81 | url = "https://" + settings['instance'] + ".service-now.com/" + table + ".do?JSONv2" 82 | url += "&sysparm_action=insert" 83 | 84 | return json.loads(http_post(settings, url, data)) 85 | 86 | 87 | # noinspection PyUnresolvedReferences,PyTypeChecker 88 | def get_list(settings, table, query, fields="name"): 89 | try: 90 | query_params = []; 91 | 92 | url = "https://" + settings['instance'] + ".service-now.com/api/now/table/" + table 93 | 94 | if fields!="": 95 | query_params.append("sysparm_fields=" + fields) 96 | 97 | if query!="": 98 | query_params.append("sysparm_query=" + urllib.parse.quote(query.encode("utf-8"))) 99 | 100 | 101 | url = url + "?" + "&".join(query_params) 102 | result_body = http_get(settings, url) 103 | 104 | response_data = json.loads(result_body)['result'] 105 | 106 | return response_data 107 | except: 108 | print("An Error occurred while attempting to connect to the instance.") 109 | return False 110 | 111 | def get_records_for_folder(folder, settings, query): 112 | if settings is not False: 113 | get_all_records(folder, settings, query) 114 | 115 | for name in os.listdir(folder): 116 | if os.path.isdir(os.path.join(folder, name)): 117 | if not is_multi(folder): 118 | get_records_for_folder(os.path.join(folder, name), settings, query) 119 | return 120 | 121 | 122 | def get_all_records(folder, settings, query): 123 | table_settings = load_settings(folder) 124 | 125 | if settings is not False and table_settings is not False: 126 | 127 | if 'table' not in table_settings: 128 | print("Folder was not a ServiceNow Folder: "+folder) 129 | return 130 | 131 | print("Grabbing Records for folder: "+folder) 132 | 133 | items = get_list(settings, table_settings['table'], query) 134 | 135 | if 'sub' in table_settings: 136 | return 137 | 138 | if 'multi' in table_settings: 139 | for item in items: 140 | for field in os.listdir(folder): 141 | field_path = os.path.join(folder, field) 142 | 143 | if os.path.isdir(field_path): 144 | sub_settings = load_settings(field_path) 145 | body_field = field 146 | name_field = sub_settings['display'] 147 | extension = sub_settings['extension'] 148 | 149 | name = item[name_field] + "." + extension 150 | 151 | doc = item[body_field] 152 | 153 | file_name = os.path.join(folder, field, convert_file_name(name)) 154 | 155 | save_if_newer(folder, item['sys_id'], name, file_name, doc, item['sys_updated_on']) 156 | 157 | else: 158 | for item in items: 159 | body_field = table_settings['body_field'] 160 | name_field = table_settings['display'] 161 | extension = table_settings['extension'] 162 | name = item[name_field] + "." + extension 163 | 164 | doc = item[body_field] 165 | file_name = os.path.join(folder, convert_file_name(name)) 166 | 167 | save_if_newer(folder, item['sys_id'], name, file_name, doc, item['sys_updated_on']) 168 | 169 | return -------------------------------------------------------------------------------- /Common/utils.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import urllib.parse 4 | import urllib.request 5 | import urllib.error 6 | import re 7 | import base64 8 | import json 9 | import os 10 | import webbrowser 11 | import difflib 12 | import codecs 13 | from datetime import datetime 14 | import time 15 | from Common.rest_client import * 16 | 17 | table_field_list = '{"asmt_metric": [{"field": "script","extension": "js"}],"automation_pipeline_transformer_javascript": [{"field": "script","extension": "js"}],"bsm_chart": [{"field": "chart","extension": "js"}, {"field": "filter","extension": "js"}],"bsm_context_menu": [{"field": "script","extension": "js"}],"bsm_map_filter": [{"field": "filter","extension": "js"}],"chat_queue": [{"field": "not_available","extension": "html"}],"ci_identifier": [{"field": "script","extension": "js"}],"clm_contract_history": [{"field": "terms_and_conditions","extension": "html"}],"clone_cleanup_script": [{"field": "script","extension": "js"}],"cmdb_ci_outage": [{"field": "details","extension": "html"}],"cmdb_ci_translation_rule": [{"field": "rule","extension": "js"}],"cmdb_depreciation": [{"field": "script","extension": "js"}],"cmdb_sw_license_calculation": [{"field": "calculation","extension": "js"}],"cmn_map_page": [{"field": "script","extension": "js"}],"cmn_relative_duration": [{"field": "script","extension": "js"}],"cmn_schedule_page": [{"field": "client_script","extension": "js"}, {"field": "server_script","extension": "js"}],"connect_action": [{"field": "script","extension": "js"}],"content_block": [{"field": "condition","extension": "js"}],"content_block_detail": [{"field": "script","extension": "js"}],"content_block_lists": [{"field": "script","extension": "js"}],"content_block_programmatic": [{"field": "programmatic_content","extension": "html"}],"content_css": [{"field": "style","extension": "css"}],"content_page_rule": [{"field": "advanced_condition","extension": "js"}],"content_type_detail": [{"field": "script","extension": "js"}],"cxs_table_config": [{"field": "search_as_script","extension": "js"}],"diagrammer_action": [{"field": "script","extension": "js"}],"discovery_classy": [{"field": "script","extension": "js"}],"discovery_port_probe": [{"field": "script","extension": "js"}],"discovery_probes": [{"field": "script","extension": "js"}, {"field": "post_processor_script","extension": "js"}],"ecc_agent_capability_value_test": [{"field": "script","extension": "js"}],"ecc_agent_ext": [{"field": "script","extension": "js"}],"ecc_agent_script": [{"field": "script","extension": "js"}],"ecc_agent_script_include": [{"field": "script","extension": "js"}],"ecc_handler": [{"field": "condition_script","extension": "js"}, {"field": "script","extension": "js"}],"expert_panel_template": [{"field": "script","extension": "js"}],"expert_panel_transition": [{"field": "transition_script","extension": "js"}],"fm_distribution_cost_rule": [{"field": "script","extension": "js"}],"fm_expense_allocation_rule": [{"field": "script","extension": "js"}],"item_option_new": [{"field": "description","extension": "html"}],"itil_agreement_ci": [{"field": "description","extension": "html"}],"itil_agreement_target_ci": [{"field": "description","extension": "html"}],"kb_submission": [{"field": "text","extension": "html"}],"live_table_notification": [{"field": "before_script","extension": "js"}],"metric_definition": [{"field": "script","extension": "js"}],"ngbsm_context_menu": [{"field": "script","extension": "js"}],"ngbsm_filter": [{"field": "filter","extension": "js"}],"ngbsm_script": [{"field": "script_plain","extension": "js"}],"ola": [{"field": "performance","extension": "html"}],"pa_scripts": [{"field": "script","extension": "js"}],"planned_change_validation_script": [{"field": "script","extension": "js"}],"planned_task": [{"field": "html_description","extension": "html"}],"pm_portfolio_project": [{"field": "html_description","extension": "html"}],"process_guide": [{"field": "advanced_condition","extension": "js"}],"process_step_approval": [{"field": "approval_script","extension": "js"}, {"field": "approver_script","extension": "js"}, {"field": "rejection_script","extension": "js"}],"proposed_change_verification_rules": [{"field": "rule_script","extension": "js"}],"question": [{"field": "default_html_value","extension": "html"}],"release_feature": [{"field": "documentation","extension": "html"}],"release_task": [{"field": "documentation","extension": "html"}],"risk_conditions": [{"field": "advanced_condition","extension": "js"}],"sc_cart_item": [{"field": "hints","extension": "html"}],"sc_cat_item": [{"field": "delivery_plan_script","extension": "js"}, {"field": "entitlement_script","extension": "js"}],"sc_cat_item_delivery_task": [{"field": "condition_script","extension": "js"}, {"field": "generation_script","extension": "js"}],"sc_cat_item_dt_approval": [{"field": "approval_script","extension": "js"}],"sc_cat_item_guide": [{"field": "validator","extension": "js"}, {"field": "script","extension": "js"}],"sc_cat_item_option": [{"field": "description","extension": "html"}],"sc_cat_item_producer": [{"field": "script","extension": "js"}],"sc_category": [{"field": "entitlement_script","extension": "js"}],"sc_ic_aprvl_type_defn": [{"field": "approver_script","extension": "js"}],"sc_ic_aprvl_type_defn_staging": [{"field": "approver_script","extension": "js"}],"sc_ic_task_assign_defn": [{"field": "assignment_script","extension": "js"}],"sc_ic_task_assign_defn_staging": [{"field": "assignment_script","extension": "js"}],"scheduled_data_export": [{"field": "pre_script","extension": "js"}],"scheduled_data_export": [{"field": "post_script","extension": "js"}],"sf_sm_order": [{"field": "auto_script_script","extension": "js"}, {"field": "ui_action_script","extension": "js"}],"sf_sm_task": [{"field": "auto_script_script","extension": "js"}, {"field": "ui_action_script","extension": "js"}],"sf_state_flow": [{"field": "automatic_script","extension": "js"}, {"field": "manual_script","extension": "js"}],"signature_image": [{"field": "signed_name","extension": "html"}, {"field": "data","extension": "js"}],"sla": [{"field": "reponsibilities","extension": "html"}, {"field": "change_procedures","extension": "html"}, {"field": "disaster_recovery","extension": "html"}, {"field": "notes","extension": "html"}, {"field": "description","extension": "html"}, {"field": "signatures","extension": "html"}, {"field": "service_goals","extension": "html"}, {"field": "security_notes","extension": "html"}, {"field": "incident_procedures","extension": "html"}],"sn_sec_cmn_calculator": [{"field": "script_values","extension": "js"}, {"field": "advanced_condition","extension": "js"}],"sn_sec_cmn_integration": [{"field": "integration_factory_script","extension": "js"}, {"field": "processor_factory_script","extension": "js"}],"sn_si_incident": [{"field": "pir","extension": "html"}],"sn_si_severity_calculator": [{"field": "advanced_condition","extension": "js"}],"sp_angular_provider": [{"field": "script","extension": "js"}],"sp_css": [{"field": "css","extension": "css"}],"sp_instance": [{"field": "css","extension": "css"}, {"field": "widget_parameters","extension": "js"}],"sp_ng_template": [{"field": "template","extension": "html"}],"sp_page": [{"field": "css","extension": "css"}],"sp_portal": [{"field": "quick_start_config","extension": "js"}],"sp_rectangle_menu_item": [{"field": "record_script","extension": "js"}],"sp_widget": [{"field": "template","extension": "html"}, {"field": "css","extension": "css"}, {"field": "link","extension": "js"}, {"field": "client_script","extension": "js"}, {"field": "script","extension": "js"}],"sys_amb_processor": [{"field": "user_subscribe_listener","extension": "js"}, {"field": "channel_create_advanced_authorization","extension": "js"}, {"field": "message_send_advanced_authorization","extension": "js"}, {"field": "user_unsubscribe_listener","extension": "js"}, {"field": "message_send_listener","extension": "js"}, {"field": "message_receive_listener","extension": "js"}, {"field": "channel_subscribe_advanced_authorization","extension": "js"}, {"field": "message_receive_advanced_authorization","extension": "js"}],"sys_dictionary": [{"field": "calculation","extension": "js"}],"sys_email_canned_message": [{"field": "body","extension": "html"}],"sys_embedded_help_action": [{"field": "onclick","extension": "js"}],"sys_home": [{"field": "text","extension": "html"}],"sys_impex_entry": [{"field": "script","extension": "js"}],"sys_impex_map": [{"field": "script","extension": "js"}],"sys_installation_exit": [{"field": "script","extension": "js"}],"sys_listener_detail": [{"field": "script","extension": "js"}],"sys_nav_link": [{"field": "url_script","extension": "js"}, {"field": "text_script","extension": "js"}],"sys_navigator": [{"field": "script","extension": "js"}],"sys_process_flow": [{"field": "description","extension": "html"}],"sys_processor": [{"field": "script","extension": "js"}],"sys_push_notif_act_script": [{"field": "script","extension": "js"}],"sys_push_notif_msg_content": [{"field": "script","extension": "js"}],"sys_relationship": [{"field": "insert_callback","extension": "js"}, {"field": "apply_to","extension": "js"}, {"field": "query_from","extension": "js"}, {"field": "query_with","extension": "js"}],"sys_report": [{"field": "content","extension": "html"}],"sys_script": [{"field": "script","extension": "js"}],"sys_script_ajax": [{"field": "script","extension": "js"}],"sys_script_client": [{"field": "script","extension": "js"}],"sys_script_email": [{"field": "script","extension": "js"}],"sys_script_fix": [{"field": "script","extension": "js"}],"sys_script_include": [{"field": "script","extension": "js"}],"sys_script_validator": [{"field": "validator","extension": "js"}],"sys_security_acl": [{"field": "script","extension": "js"}],"sys_service_api": [{"field": "script","extension": "js"}],"sys_soap_message": [{"field": "wsdl_xml","extension": "html"}],"sys_soap_message_function": [{"field": "envelope","extension": "html"}],"sys_soap_message_import": [{"field": "external_document","extension": "html"}],"sys_soap_message_test": [{"field": "response","extension": "html"}, {"field": "request","extension": "html"}],"sys_transform_entry": [{"field": "source_script","extension": "js"}],"sys_transform_map": [{"field": "script","extension": "js"}],"sys_transform_script": [{"field": "script","extension": "js"}],"sys_ui_action": [{"field": "script","extension": "js"}],"sys_ui_context_menu": [{"field": "dynamic_actions_script","extension": "js"}, {"field": "on_show_script","extension": "js"}, {"field": "action_script","extension": "js"}],"sys_ui_list_control": [{"field": "edit_condition","extension": "js"}, {"field": "new_condition","extension": "js"}, {"field": "link_condition","extension": "js"}, {"field": "empty_condition","extension": "js"}, {"field": "filter_condition","extension": "js"}, {"field": "columns_condition","extension": "js"}],"sys_ui_list_control_embedded": [{"field": "new_condition","extension": "js"}, {"field": "empty_condition","extension": "js"}, {"field": "columns_condition","extension": "js"}, {"field": "link_condition","extension": "js"}],"sys_ui_list_script_client": [{"field": "script","extension": "js"}],"sys_ui_list_script_server": [{"field": "script","extension": "js"}],"sys_ui_macro": [{"field": "xml","extension": "html"}],"sys_ui_ng_action": [{"field": "script","extension": "js"}],"sys_ui_page": [{"field": "html","extension": "html"}, {"field": "processing_script","extension": "js"}, {"field": "client_script","extension": "js"}],"sys_ui_policy": [{"field": "script_true","extension": "js"}, {"field": "script_false","extension": "js"}],"sys_ui_script": [{"field": "script","extension": "js"}],"sys_ui_title": [{"field": "script","extension": "js"}],"sys_update_diff": [{"field": "payload_diff","extension": "html"}],"sys_update_preview_xml": [{"field": "payload_diff","extension": "html"}],"sys_upgrade_history_log": [{"field": "payload_diff","extension": "html"}],"sys_web_service": [{"field": "script","extension": "js"}],"sys_widgets": [{"field": "script","extension": "js"}],"sys_ws_operation": [{"field": "operation_script","extension": "js"}],"sysauto": [{"field": "condition","extension": "js"}],"sysauto_report": [{"field": "report_body","extension": "html"}],"sysauto_script": [{"field": "script","extension": "js"}],"sysevent_email_action": [{"field": "message_html","extension": "html"}, {"field": "advanced_condition","extension": "js"}],"sysevent_email_style": [{"field": "style","extension": "html"}],"sysevent_email_template": [{"field": "message_html","extension": "html"}],"sysevent_in_email_action": [{"field": "reply_email","extension": "html"}, {"field": "script","extension": "js"}],"sysevent_script_action": [{"field": "script","extension": "js"}],"sysrule_escalate": [{"field": "advanced_condition","extension": "js"}, {"field": "assignment_script","extension": "js"}],"sysrule_view": [{"field": "script","extension": "js"}],"task_activity": [{"field": "message","extension": "html"}],"tm_test": [{"field": "test_data","extension": "html"}, {"field": "test_description","extension": "html"}],"tm_test_case": [{"field": "prereq","extension": "html"}],"tm_test_case_instance": [{"field": "prereq","extension": "html"}],"tm_test_instance": [{"field": "test_description","extension": "html"}],"tm_test_plan": [{"field": "instructions","extension": "html"}],"treemap_metric": [{"field": "custom_script","extension": "js"}, {"field": "click_through_url_script","extension": "js"}],"u_field_testing": [{"field": "u_html","extension": "html"}],"usageanalytics_count_cfg": [{"field": "script","extension": "js"}],"user_criteria": [{"field": "script","extension": "js"}],"wf_activity_definition": [{"field": "script","extension": "js"}],"wf_element_activity": [{"field": "input_process_script","extension": "js"}, {"field": "processing_script","extension": "js"}, {"field": "output_process_script","extension": "js"}],"wf_workflow_version": [{"field": "on_cancel","extension": "js"}],"workbench_config": [{"field": "phase_actions","extension": "js"}]}' 18 | 19 | def write_doc_file(the_file, doc): 20 | f = open(the_file, 'wb') 21 | f.write(bytes(doc, 'utf-8')) 22 | f.close() 23 | 24 | return 25 | 26 | 27 | def is_sn(dirs): 28 | if len(dirs) == 0: 29 | return False 30 | 31 | settings_file = os.path.join(dirs[0], "service-now.json") 32 | return os.path.exists(settings_file) 33 | 34 | 35 | def convert_time(timestamp): 36 | return datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S") 37 | 38 | 39 | def save_if_newer(the_dir, the_id, name, the_file, doc, timestamp=False): 40 | settings = load_settings(the_dir) 41 | update_time = datetime.now() 42 | prompt = False 43 | message = "" 44 | 45 | if timestamp is not False: 46 | update_time = convert_time(timestamp) 47 | 48 | # 'Get the files date here 49 | file_name = os.path.join(the_dir, convert_file_name(name)) 50 | 51 | print("Grabbing File: "+name) 52 | 53 | if os.path.isfile(file_name): 54 | if name in settings['files']: 55 | if 'update_time' in settings['files'][name]: 56 | file_time = datetime.fromtimestamp(settings['files'][name]['update_time']) 57 | if update_time > file_time: 58 | prompt = True 59 | message = "There's a NEWER version of File '" + name + "' on the instance.\nOverwrite?" 60 | if file_time > update_time: 61 | prompt = True 62 | message = "There's an OLDER version of File '" + name + "' on the instance.\nOverwrite?" 63 | else: 64 | prompt = True 65 | message = "File '" + name + "' has no timestamp on record, and may be out of sync with the instance.\nOverwrite?" 66 | 67 | if prompt is not False: 68 | if sublime.ok_cancel_dialog(message) is False: 69 | return False 70 | 71 | write_doc_file(the_file, doc) 72 | add_file(the_dir, the_id, name, time.mktime(update_time.timetuple())) 73 | return True 74 | 75 | 76 | def convert_file_name(name): 77 | name = name.replace("/", "_").replace("\\", "_").replace("\0", "_").replace("<", "_").replace(">", "_").replace(":", "_").replace('"', "_") 78 | name = name.replace("|", "_").replace("?", "_").replace("*", "_").replace("#", "_").replace("$", "_").replace("+", "_").replace("%", "_") 79 | name = name.replace("!", "_").replace("`", "_").replace("&", "_").replace("{", "_").replace("}", "_").replace("'", "_").replace("=", "_") 80 | name = name.replace("@", "_") 81 | return name 82 | 83 | 84 | def lookup_record_id(name, settings): 85 | if 'files' not in settings: 86 | return False 87 | 88 | if name in settings['files']: 89 | return settings['files'][name]['sys_id'] 90 | 91 | for file in settings['files']: 92 | if 'file_name' in file: 93 | if file['file_name'] == name: 94 | return file['sys_id'] 95 | 96 | return False 97 | 98 | 99 | def is_multi(the_dir): 100 | settings = load_settings(the_dir) 101 | 102 | if settings is not False: 103 | if hasattr(settings, 'multi') or 'multi' in settings: 104 | return True 105 | 106 | return False 107 | 108 | 109 | def add_file(the_dir, the_id, name, update_time=time.time()): 110 | file_name = convert_file_name(name) 111 | 112 | settings = load_settings(the_dir) 113 | 114 | if 'files' not in settings: 115 | settings['files'] = dict() 116 | 117 | if name in settings['files']: 118 | settings['files'][name]['sys_id'] = the_id 119 | settings['files'][name]['update_time'] = update_time 120 | settings['files'][name]['file_name'] = file_name 121 | else: 122 | settings['files'][name] = dict() 123 | settings['files'][name]['sys_id'] = the_id 124 | settings['files'][name]['update_time'] = update_time 125 | settings['files'][name]['file_name'] = file_name 126 | 127 | save_settings(the_dir, settings) 128 | 129 | 130 | def load_settings(the_dir): 131 | settings_file = os.path.join(the_dir, "service-now.json") 132 | 133 | if os.path.exists(settings_file) is False: 134 | return False 135 | 136 | f = open(settings_file, 'r') 137 | json_data = f.read() 138 | settings = json.loads(json_data) 139 | 140 | return settings 141 | 142 | 143 | def save_settings(the_dir, settings): 144 | settings_file = os.path.join(the_dir, "service-now.json") 145 | f = open(settings_file, 'wb') 146 | f.write(bytes(json.dumps(settings, indent=4), 'utf-8')) 147 | f.close() 148 | 149 | 150 | def save_setting(the_dir, key, value): 151 | settings_file = os.path.join(the_dir, "service-now.json") 152 | settings = load_settings(the_dir) 153 | settings[key] = value 154 | 155 | f = open(settings_file, 'wb') 156 | f.write(bytes(json.dumps(settings, indent=4), 'utf-8')) 157 | f.close() 158 | 159 | return True 160 | 161 | def diff_and_confirm(full_file_name, remote_doc, local_doc): 162 | diffs = diff_file_to_doc(full_file_name, remote_doc) 163 | 164 | if diffs: 165 | diffs = diff_doc_to_doc(local_doc, remote_doc) 166 | if diffs: 167 | action = sublime.yes_no_cancel_dialog("Remote record does not match local.", "Overwrite Remote", "View Differences") 168 | 169 | if action == sublime.DIALOG_NO: 170 | diffs = map(lambda line: (line and line[-1] == "\n") and line or line + "\n", diffs) 171 | diffs = ''.join(diffs) 172 | scratch = view.window().new_file() 173 | scratch.set_scratch(True) 174 | scratch.run_command('create_diff_scratch', {'content': diffs}) 175 | 176 | return False 177 | 178 | return True 179 | 180 | def diff_file_to_doc(full_file_name, doc): 181 | f = codecs.open(full_file_name, 'r', "utf-8") 182 | original_doc = prep_content(f.read()) 183 | f.close() 184 | new_doc = prep_content(doc) 185 | diffs = list(difflib.unified_diff(original_doc, new_doc)) 186 | 187 | return diffs 188 | 189 | 190 | def diff_doc_to_doc(original_doc, doc): 191 | original_doc = prep_content(original_doc) 192 | new_doc = prep_content(doc) 193 | diffs = list(difflib.unified_diff(original_doc, new_doc)) 194 | 195 | return diffs 196 | 197 | 198 | def get_tables(settings): 199 | list_data = get_list(settings, "sys_documentation", "element=NULL^nameNOT%20LIKEts_c^language=en^nameNOT%20LIKE0","label,name") 200 | 201 | return list_data 202 | 203 | 204 | def prep_content(ab): 205 | content = ab.splitlines(True) 206 | content = [line.replace("\r\n", "\n").replace("\r", "\n") for line in content] 207 | content = [line.rstrip() for line in content] 208 | 209 | return content 210 | 211 | 212 | def get_file_path(full_file_name): 213 | file_pieces = re.split(r"(?i)[\\/]", full_file_name) 214 | file_pieces.pop() 215 | full_path = '/'.join(file_pieces) 216 | 217 | return full_path 218 | 219 | 220 | def get_file_name(full_file_name): 221 | file_pieces = re.split(r"(?i)[\\/]", full_file_name) 222 | file_name = file_pieces[len(file_pieces) - 1] 223 | 224 | return file_name 225 | 226 | 227 | def get_file_type(full_file_name): 228 | file_pieces = re.split(r"(?i)[\\/]", full_file_name) 229 | file_type = file_pieces[len(file_pieces) - 2] 230 | 231 | return file_type 232 | 233 | 234 | def load_multiple(settings, table_settings, file_dir, query): 235 | 236 | if settings!=False and table_settings!=False: 237 | fields = ['sys_id'] 238 | if "display" in table_settings: 239 | fields.append(table_settings["display"]) 240 | 241 | if "body_field" in table_settings: 242 | fields.append(table_settings["body_field"]) 243 | 244 | if "fields" in table_settings: 245 | for field in table_settings["fields"]: 246 | fields.append(field['field']) 247 | 248 | if 'multi' in table_settings: 249 | for child in os.listdir(file_dir): 250 | test_path = os.path.join(file_dir, child) 251 | if os.path.isdir(test_path): 252 | fields.append(child) 253 | 254 | fields = ",".join(fields); 255 | 256 | items = get_list(settings, table_settings['table'], query, fields) 257 | 258 | action = sublime.yes_no_cancel_dialog("Action will add/change " + str(len(items)) + " files. Continue?") 259 | 260 | if action!=sublime.DIALOG_YES: 261 | return 262 | 263 | 264 | if 'grouped' in table_settings: 265 | for item in items: 266 | name_field = table_settings['display'] 267 | name = item[name_field] 268 | name = re.sub('[^-a-zA-Z0-9_.() ]+', '', name) 269 | 270 | grouped_dir = os.path.join(file_dir, name) 271 | 272 | if os.path.exists(grouped_dir): 273 | grouped_dir = grouped_dir + "_" + item['sys_id'] 274 | 275 | os.makedirs(grouped_dir) 276 | settings = json.loads('{}') 277 | settings['grouped_child'] = True 278 | settings['id'] = item['sys_id'] 279 | save_settings(grouped_dir, settings) 280 | 281 | for child in table_settings['fields']: 282 | child_name = child['name']; 283 | child_field = child['field']; 284 | extension = child['extension'] 285 | child_file = convert_file_name(child_name + "." + extension) 286 | file_name = os.path.join(grouped_dir , child_file) 287 | 288 | if os.path.exists(file_name): 289 | if sublime.ok_cancel_dialog("File already exists.\nOverwrite?")==False: 290 | return False 291 | 292 | doc = item[child_field] 293 | write_doc_file(file_name, doc) 294 | 295 | if 'multi' in table_settings: 296 | for item in items: 297 | for field in os.listdir(file_dir): 298 | field_path = os.path.join(file_dir, field) 299 | 300 | if os.path.isdir(field_path): 301 | sub_settings = load_settings( field_path ) 302 | body_field = field 303 | name_field = sub_settings['display'] 304 | extension = sub_settings['extension'] 305 | 306 | name = convert_file_name(item[name_field] + "." + extension) 307 | 308 | 309 | doc = item[body_field] 310 | 311 | file_name = os.path.join(file_dir,field, name) 312 | 313 | if os.path.exists(file_name): 314 | if sublime.ok_cancel_dialog("File already exists.\nOverwrite?")==False: 315 | return False 316 | 317 | write_doc_file(file_name, doc) 318 | 319 | add_file(field_path ,item['sys_id'],name) 320 | 321 | else: 322 | for item in items: 323 | body_field = table_settings['body_field'] 324 | name_field = table_settings['display'] 325 | extension = table_settings['extension'] 326 | name = convert_file_name(item[name_field] + "." + extension) 327 | 328 | doc = item[body_field] 329 | file_name = os.path.join(file_dir, name) 330 | 331 | if os.path.exists(file_name): 332 | if sublime.ok_cancel_dialog(file_name + " already exists.\nOverwrite?")==False: 333 | return False 334 | 335 | write_doc_file(file_name, doc) 336 | 337 | add_file(file_dir,item['sys_id'],name) 338 | 339 | return -------------------------------------------------------------------------------- /Context.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { "caption": "-", "id": "snow" }, 3 | // { "id": "bgscript", "caption": "Run As Background Script", "command": "run_background_script" }, 4 | { "command": "open_snow_record", "caption": "Open ServiceNow Record" } 5 | ] 6 | -------------------------------------------------------------------------------- /Events/__init__.py: -------------------------------------------------------------------------------- 1 | # from .save_listener import * -------------------------------------------------------------------------------- /Events/save_listener.py: -------------------------------------------------------------------------------- 1 | from Common.utils import * 2 | import sublime 3 | import sublime_plugin 4 | 5 | class ServiceNowListener(sublime_plugin.EventListener): 6 | def on_pre_save(self, view): 7 | if len(view.window().folders()) > 0: 8 | working_dir = view.window().folders()[0] 9 | settings = load_settings(working_dir) 10 | 11 | if settings is not False: 12 | updated_record = False 13 | full_file_name = view.file_name() 14 | file_path = get_file_path(full_file_name) 15 | file_name = get_file_name(full_file_name) 16 | file_type = get_file_type(full_file_name) 17 | file_name_no_ext = file_name.split(".")[0]; 18 | 19 | if file_name == "service-now.json" or file_type == "spec": 20 | return False 21 | 22 | table_settings = load_settings(file_path) 23 | 24 | if table_settings is False: 25 | return False 26 | 27 | reg = sublime.Region(0, view.size()) 28 | text = view.substr(reg) 29 | data = dict() 30 | 31 | if 'grouped_child' in table_settings: 32 | child_settings = table_settings 33 | parent_path = file_path[::-1] 34 | parent_path = parent_path.split('/',1)[1] 35 | parent_path = parent_path[::-1] 36 | table_settings = load_settings(parent_path) 37 | 38 | for field in table_settings['fields']: 39 | 40 | if(field['name'] == file_name_no_ext): 41 | sys_id = child_settings['id'] 42 | record = get_record(settings, table_settings['table'], sys_id) 43 | 44 | if record is not False: 45 | body_field = field['field'] 46 | name = full_file_name 47 | data[body_field] = text 48 | doc = record[body_field] 49 | 50 | continue_update = diff_and_confirm(full_file_name, doc, text); 51 | 52 | if continue_update is True: 53 | updated_record = update_record(settings, data, table_settings['table'],sys_id) 54 | 55 | else: 56 | data[table_settings['body_field']] = text 57 | 58 | if file_name in table_settings['files']: 59 | sys_id = table_settings['files'][file_name]['sys_id'] 60 | record = get_record(settings, table_settings['table'], sys_id) 61 | 62 | if record is not False: 63 | body_field = table_settings['body_field'] 64 | name = full_file_name 65 | 66 | doc = record[body_field] 67 | 68 | continue_update = diff_and_confirm(full_file_name, doc, text); 69 | 70 | if continue_update is True: 71 | updated_record = update_record(settings, data, table_settings['table'],sys_id) 72 | 73 | else: 74 | data[table_settings['display']] = str.replace(file_name,"." + table_settings['extension'],"") 75 | updated_record = create_record(settings, data, table_settings['table']) 76 | 77 | if updated_record is not False: 78 | print("Updated at " + updated_record['sys_updated_on']) 79 | add_file(file_path, updated_record['sys_id'], file_name, time.mktime(convert_time(updated_record['sys_updated_on']).timetuple())) 80 | 81 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 salcosta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [{ 2 | 3 | "caption": "ServiceNow Sync", 4 | "mnemonic": "w", 5 | "id": "servicenow", 6 | "children": [{ 7 | "id": "create", 8 | "caption": "Connect To ServiceNow", 9 | "command": "create_connection", 10 | "args": {} 11 | }, { 12 | "id": "test", 13 | "caption": "Test Connection", 14 | "command": "test_connection" 15 | }, 16 | { 17 | "id": "auth", 18 | "caption": "Enter Credentials", 19 | "command": "enter_credentials" 20 | }, 21 | { 22 | "id": "load_snow_table", 23 | "caption": "Sync Table", 24 | "command": "load_snow_table" 25 | }] 26 | }] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # servicenow-sync 2 | ServiceNow Sublime Text 3 Integration 3 | -------------------------------------------------------------------------------- /Reloader/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salcosta/servicenow-sync/aa0592e50dae1d57d1654969be702fcc25214c7d/Reloader/__init__.py -------------------------------------------------------------------------------- /Reloader/module_loader.py: -------------------------------------------------------------------------------- 1 | import imp, os, sys 2 | 3 | def load ( moduleDirectories, pluginGlobals ): 4 | 5 | moduleExtensions = [ "py" ] 6 | moduleLoader_Name = "module_loader" 7 | modulePaths = [] 8 | 9 | for path in sys.path: 10 | print(path) 11 | if path.endswith ( "Sublime Text 3" + os.sep + "Installed Packages" + os.sep + "servicenow-sync.sublime-package" ): 12 | packagesPath = path 13 | break 14 | 15 | for directory in moduleDirectories: 16 | modulePaths.append ( packagesPath + os.sep + directory ) 17 | 18 | for index in range ( 0, 2 ): 19 | for path in modulePaths: 20 | for file in os.listdir ( path ): 21 | for extension in moduleExtensions: 22 | 23 | if file.endswith ( os.extsep + extension ): 24 | moduleName = os.path.basename( file )[ : - len ( os.extsep + extension ) ] 25 | 26 | if moduleName != moduleLoader_Name: 27 | fileObject, file, description = imp.find_module( moduleName, [ path ] ) 28 | pluginGlobals[ moduleName ] = imp.load_module ( moduleName, fileObject, file, description ) -------------------------------------------------------------------------------- /ServiceNowSync.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import sublime, sublime_plugin 4 | import imp 5 | 6 | plugin = os.path.realpath(__file__) 7 | directory = os.path.dirname(plugin) 8 | 9 | if directory not in sys.path: 10 | sys.path.append(directory) 11 | 12 | from Commands.create_connection import * 13 | from Commands.create_diff_scratch import * 14 | from Commands.enter_credentials import * 15 | from Commands.load_all_snow_record import * 16 | from Commands.load_snow_record import * 17 | from Commands.load_snow_table import * 18 | from Commands.load_modified_records import * 19 | from Commands.open_service_now_record import * 20 | from Commands.refresh_snow_record import * 21 | from Commands.test_connection import * 22 | from Events.save_listener import * -------------------------------------------------------------------------------- /Side Bar.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { "caption": "-", "id": "snow" }, 3 | { "caption": "Sync Record", "command": "load_snow_record", "args": {"dirs": []} }, 4 | { "caption": "Sync Multiple Records", "command": "load_all_snow_record", "args": {"dirs": []} }, 5 | { "caption": "Compare Record To Server", "command": "refresh_snow_record", "args": {"files": []} } 6 | ] 7 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salcosta/servicenow-sync/aa0592e50dae1d57d1654969be702fcc25214c7d/__init__.py --------------------------------------------------------------------------------