diff options
Diffstat (limited to 'zenmap/zenmapGUI/ScriptInterface.py')
-rw-r--r-- | zenmap/zenmapGUI/ScriptInterface.py | 706 |
1 files changed, 706 insertions, 0 deletions
diff --git a/zenmap/zenmapGUI/ScriptInterface.py b/zenmap/zenmapGUI/ScriptInterface.py new file mode 100644 index 0000000..b22651a --- /dev/null +++ b/zenmap/zenmapGUI/ScriptInterface.py @@ -0,0 +1,706 @@ +#!/usr/bin/env python3 + +# ***********************IMPORTANT NMAP LICENSE TERMS************************ +# * +# * The Nmap Security Scanner is (C) 1996-2023 Nmap Software LLC ("The Nmap +# * Project"). Nmap is also a registered trademark of the Nmap Project. +# * +# * This program is distributed under the terms of the Nmap Public Source +# * License (NPSL). The exact license text applying to a particular Nmap +# * release or source code control revision is contained in the LICENSE +# * file distributed with that version of Nmap or source code control +# * revision. More Nmap copyright/legal information is available from +# * https://nmap.org/book/man-legal.html, and further information on the +# * NPSL license itself can be found at https://nmap.org/npsl/ . This +# * header summarizes some key points from the Nmap license, but is no +# * substitute for the actual license text. +# * +# * Nmap is generally free for end users to download and use themselves, +# * including commercial use. It is available from https://nmap.org. +# * +# * The Nmap license generally prohibits companies from using and +# * redistributing Nmap in commercial products, but we sell a special Nmap +# * OEM Edition with a more permissive license and special features for +# * this purpose. See https://nmap.org/oem/ +# * +# * If you have received a written Nmap license agreement or contract +# * stating terms other than these (such as an Nmap OEM license), you may +# * choose to use and redistribute Nmap under those terms instead. +# * +# * The official Nmap Windows builds include the Npcap software +# * (https://npcap.com) for packet capture and transmission. It is under +# * separate license terms which forbid redistribution without special +# * permission. So the official Nmap Windows builds may not be redistributed +# * without special permission (such as an Nmap OEM license). +# * +# * Source is provided to this software because we believe users have a +# * right to know exactly what a program is going to do before they run it. +# * This also allows you to audit the software for security holes. +# * +# * Source code also allows you to port Nmap to new platforms, fix bugs, and add +# * new features. You are highly encouraged to submit your changes as a Github PR +# * or by email to the dev@nmap.org mailing list for possible incorporation into +# * the main distribution. Unless you specify otherwise, it is understood that +# * you are offering us very broad rights to use your submissions as described in +# * the Nmap Public Source License Contributor Agreement. This is important +# * because we fund the project by selling licenses with various terms, and also +# * because the inability to relicense code has caused devastating problems for +# * other Free Software projects (such as KDE and NASM). +# * +# * The free version of Nmap is distributed in the hope that it will be +# * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Warranties, +# * indemnification and commercial support are all available through the +# * Npcap OEM program--see https://nmap.org/oem/ +# * +# ***************************************************************************/ + +# This module is responsible for interface present under "Scripting" tab. + +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, GLib + +import os +import tempfile + +# Prevent loading PyXML +import xml +xml.__path__ = [x for x in xml.__path__ if "_xmlplus" not in x] + +import xml.sax + +from zenmapGUI.higwidgets.higboxes import HIGVBox, HIGHBox +from zenmapGUI.higwidgets.higscrollers import HIGScrolledWindow +from zenmapGUI.higwidgets.higbuttons import HIGButton +from zenmapCore.ScriptMetadata import get_script_entries +from zenmapCore.ScriptArgsParser import parse_script_args_dict +from zenmapCore.NmapCommand import NmapCommand +from zenmapCore.NmapOptions import NmapOptions +import zenmapCore.NSEDocParser +import zenmapGUI.FileChoosers +from zenmapCore.UmitConf import PathsConfig +from zenmapCore.UmitLogging import log +from zenmapCore.Name import APP_NAME + +paths_config = PathsConfig() + + +def text_buffer_insert_nsedoc(buf, nsedoc): + """Inserts NSEDoc at the end of the buffer, with markup turned into proper + tags.""" + if not buf.get_tag_table().lookup("NSEDOC_CODE_TAG"): + buf.create_tag("NSEDOC_CODE_TAG", font="Monospace") + for event in zenmapCore.NSEDocParser.nsedoc_parse(nsedoc): + if event.type == "paragraph_start": + buf.insert(buf.get_end_iter(), "\n") + elif event.type == "paragraph_end": + buf.insert(buf.get_end_iter(), "\n") + elif event.type == "list_start": + buf.insert(buf.get_end_iter(), "\n") + elif event.type == "list_end": + pass + elif event.type == "list_item_start": + buf.insert(buf.get_end_iter(), "\u2022\u00a0") # bullet nbsp + elif event.type == "list_item_end": + buf.insert(buf.get_end_iter(), "\n") + elif event.type == "text": + buf.insert(buf.get_end_iter(), event.text) + elif event.type == "code": + buf.insert_with_tags_by_name( + buf.get_end_iter(), event.text, "NSEDOC_CODE_TAG") + + +class ScriptHelpXMLContentHandler (xml.sax.handler.ContentHandler): + """A very simple parser for --script-help XML output. This could extract + other information like categories and description, but all it gets is + filenames. (ScriptMetadata gets the other information.)""" + def __init__(self): + xml.sax.handler.ContentHandler.__init__(self) + self.script_filenames = [] + self.scripts_dir = None + self.nselib_dir = None + + def startElement(self, name, attrs): + if name == "directory": + if "name" not in attrs: + raise ValueError( + '"directory" element did not have "name" attribute') + dirname = attrs["name"] + if "path" not in attrs: + raise ValueError( + '"directory" element did not have "path" attribute') + path = attrs["path"] + if dirname == "scripts": + self.scripts_dir = path + elif dirname == "nselib": + self.nselib_dir = path + else: + # Ignore. + pass + elif name == "script": + if "filename" not in attrs: + raise ValueError( + '"script" element did not have "filename" attribute') + self.script_filenames.append(attrs["filename"]) + + @staticmethod + def parse_nmap_script_help(f): + parser = xml.sax.make_parser() + handler = ScriptHelpXMLContentHandler() + parser.setContentHandler(handler) + parser.parse(f) + return handler + + +class ScriptInterface: + # Timeout, in milliseconds, after the user stops typing and we update the + # interface from --script. + SCRIPT_LIST_DELAY = 500 + # Timeout, in milliseconds, between polls of the Nmap subprocess. + NMAP_DELAY = 200 + + def __init__(self, root_tabs, ops, update_command, help_buf): + self.hmainbox = HIGHBox(False, 0) + self.notscripttab = False # show profile editor it is a script tab + self.nmap_process = None + self.script_list_timeout_id = None + self.nmap_timeout_id = None + self.chk_nmap_timeout_id = None + self.script_file_chooser = None + self.ops = ops + self.update_command = update_command + self.help_buf = help_buf + self.arg_values = {} + self.current_arguments = [] + self.set_help_texts() + self.prev_script_spec = None + self.focusedentry = None + + self.liststore = Gtk.ListStore.new([str, bool, object]) + + self.file_liststore = Gtk.ListStore.new([str, bool]) + + # Arg name, arg value, (name, desc) tuple. + self.arg_liststore = Gtk.ListStore.new([str, str, object]) + + # This is what is shown initially. After the initial Nmap run to get + # the list of script is finished, this will be replaced with a TreeView + # showing the scripts or an error message. + self.script_list_container = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) + self.script_list_container.pack_start(self.make_please_wait_widget(), True, True, 0) + self.hmainbox.pack_start(self.script_list_container, False, False, 0) + + self.nmap_error_widget = Gtk.Label.new(_( + "There was an error getting the list of scripts from Nmap. " + "Try upgrading Nmap.")) + self.nmap_error_widget.set_line_wrap(True) + self.nmap_error_widget.show_all() + + self.script_list_widget = self.make_script_list_widget() + self.script_list_widget.show_all() + + vbox = HIGVBox(False, 5) + vbox.pack_start(self.make_description_widget(), True, True, 0) + vbox.pack_start(self.make_arguments_widget(), True, True, 0) + self.hmainbox.pack_end(vbox, True, True, 0) + + self.update_argument_values(self.ops["--script-args"]) + + # Start the initial backgrounded Nmap run to get the list of all + # available scripts. + self.get_script_list("all", self.initial_script_list_cb) + + def get_script_list(self, rules, callback): + """Start an Nmap subprocess in the background with + "--script-help=<rules> -oX -", and set it up to call the given callback + when finished.""" + + ops = NmapOptions() + ops.executable = paths_config.nmap_command_path + ops["--script-help"] = rules + ops["-oX"] = "-" + command_string = ops.render_string() + # Separate stderr to avoid breaking XML parsing with "Warning: File + # ./nse_main.lua exists, but Nmap is using...". + stderr = tempfile.TemporaryFile( + mode="r", prefix=APP_NAME + "-script-help-stderr-") + log.debug("Script interface: running %s" % repr(command_string)) + nmap_process = NmapCommand(command_string) + try: + nmap_process.run_scan(stderr=stderr) + except Exception as e: + callback(False, None) + stderr.close() + return + stderr.close() + + self.script_list_widget.set_sensitive(False) + + GLib.timeout_add( + self.NMAP_DELAY, self.script_list_timer_callback, + nmap_process, callback) + + def script_list_timer_callback(self, process, callback): + try: + status = process.scan_state() + except Exception: + status = None + log.debug("Script interface: script_list_timer_callback %s" % + repr(status)) + + if status is True: + # Still running, schedule this timer to check again. + return True + + self.script_list_widget.set_sensitive(True) + + if status is False: + # Finished with success. + callback(True, process) + else: + # Finished with error. + callback(False, process) + + def initial_script_list_cb(self, status, process): + log.debug("Script interface: initial_script_list_cb %s" % repr(status)) + for child in self.script_list_container.get_children(): + self.script_list_container.remove(child) + if status and self.handle_initial_script_list_output(process): + self.script_list_container.pack_start(self.script_list_widget, True, True, 0) + else: + self.script_list_container.pack_start(self.nmap_error_widget, True, True, 0) + + def handle_initial_script_list_output(self, process): + process.stdout_file.seek(0) + try: + handler = ScriptHelpXMLContentHandler.parse_nmap_script_help( + process.stdout_file) + except (ValueError, xml.sax.SAXParseException) as e: + log.debug("--script-help parse exception: %s" % str(e)) + return False + + # Check if any scripts were output; if not, Nmap is probably too old. + if len(handler.script_filenames) == 0: + return False + + if not handler.scripts_dir: + log.debug("--script-help error: no scripts directory") + return False + if not handler.nselib_dir: + log.debug("--script-help error: no nselib directory") + return False + + log.debug("Script interface: scripts dir %s" % repr( + handler.scripts_dir)) + log.debug("Script interface: nselib dir %s" % repr(handler.nselib_dir)) + + # Make a dict of script metadata entries. + entries = {} + for entry in get_script_entries( + handler.scripts_dir, handler.nselib_dir): + entries[entry.filename] = entry + + self.liststore.clear() + for filename in handler.script_filenames: + basename = os.path.basename(filename) + entry = entries.get(basename) + if entry: + script_id = self.strip_file_name(basename) + self.liststore.append([script_id, False, entry]) + else: + # ScriptMetadata found nothing for this script? + self.file_liststore.append([filename, False]) + + # Now figure out which scripts are selected. + self.update_script_list_from_spec(self.ops["--script"]) + return True + + def update_script_list_from_spec(self, spec): + """Callback method for user edit delay.""" + log.debug("Script interface: update_script_list_from_spec %s" % repr( + spec)) + if spec: + self.get_script_list(spec, self.update_script_list_cb) + else: + self.refresh_list_scripts([]) + + def update_script_list_cb(self, status, process): + log.debug("Script interface: update_script_list_cb %s" % repr(status)) + if status: + self.handle_update_script_list_output(process) + else: + self.refresh_list_scripts([]) + + def handle_update_script_list_output(self, process): + process.stdout_file.seek(0) + try: + handler = ScriptHelpXMLContentHandler.parse_nmap_script_help( + process.stdout_file) + except (ValueError, xml.sax.SAXParseException) as e: + log.debug("--script-help parse exception: %s" % str(e)) + return False + + self.refresh_list_scripts(handler.script_filenames) + + def get_hmain_box(self): + """Returns main Hbox to ProfileEditor.""" + return self.hmainbox + + def update(self): + """Updates the interface when the command entry is changed.""" + # updates list of scripts + rules = self.ops["--script"] + if (self.prev_script_spec != rules): + self.renew_script_list_timer(rules) + self.prev_script_spec = rules + # updates arguments.. + raw_argument = self.ops["--script-args"] + if raw_argument is not None: + self.parse_script_args(raw_argument) + self.arg_liststore.clear() + for arg in self.current_arguments: + arg_name, arg_desc = arg + value = self.arg_values.get(arg_name) + if not value: + self.arg_liststore.append([arg_name, None, arg]) + else: + self.arg_liststore.append([arg_name, value, arg]) + + def renew_script_list_timer(self, spec): + """Restart the timer to update the script list when the user edits the + command. Because updating the script list is an expensive operation + involving the creation of a subprocess, we don't do it for every typed + character.""" + if self.script_list_timeout_id: + GLib.source_remove(self.script_list_timeout_id) + self.script_list_timeout_id = GLib.timeout_add( + self.SCRIPT_LIST_DELAY, + self.update_script_list_from_spec, spec) + + def parse_script_args(self, raw_argument): + """When the command line is edited, this function is called to update + the script arguments display according to the value of + --script-args.""" + arg_dict = parse_script_args_dict(raw_argument) + if arg_dict is None: # if there is parsing error args_dict holds none + self.arg_values.clear() + else: + for key in arg_dict.keys(): + self.arg_values[key] = arg_dict[key] + + def update_argument_values(self, raw_argument): + """When scripting tab starts up, argument values are updated.""" + if raw_argument is not None: + self.parse_script_args(raw_argument) + + def set_help_texts(self): + """Sets the help texts to be displayed.""" + self.list_scripts_help = _("""List of scripts + +A list of all installed scripts. Activate or deactivate a script \ +by clicking the box next to the script name.""") + self.description_help = _("""Description + +This box shows the categories a script belongs to. In addition, it gives a \ +detailed description of the script which is present in script. A URL points \ +to online NSEDoc documentation.""") + self.argument_help = _("""Arguments + +A list of arguments that affect the selected script. Enter a value by \ +clicking in the value field beside the argument name.""") + + def make_please_wait_widget(self): + vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) + label = Gtk.Label.new(_("Please wait.")) + label.set_line_wrap(True) + vbox.pack_start(label, True, True, 0) + return vbox + + def make_script_list_widget(self): + """Creates and packs widgets associated with left hand side of + Interface.""" + vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) + + scrolled_window = HIGScrolledWindow() + scrolled_window.set_policy(Gtk.PolicyType.ALWAYS, Gtk.PolicyType.ALWAYS) + # Expand only vertically. + scrolled_window.set_size_request(175, -1) + listview = Gtk.TreeView.new_with_model(self.liststore) + listview.set_headers_visible(False) + listview.connect("enter-notify-event", self.update_help_ls_cb) + selection = listview.get_selection() + selection.connect("changed", self.selection_changed_cb) + cell = Gtk.CellRendererText() + togglecell = Gtk.CellRendererToggle() + togglecell.set_property("activatable", True) + togglecell.connect("toggled", self.toggled_cb, self.liststore) + col = Gtk.TreeViewColumn(title=_('Names')) + col.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY) + col.set_resizable(True) + togglecol = Gtk.TreeViewColumn(title=None, cell_renderer=togglecell) + togglecol.add_attribute(togglecell, "active", 1) + listview.append_column(togglecol) + listview.append_column(col) + col.pack_start(cell, True) + col.add_attribute(cell, "text", 0) + scrolled_window.add(listview) + scrolled_window.show() + vbox.pack_start(scrolled_window, True, True, 0) + + self.file_scrolled_window = HIGScrolledWindow() + self.file_scrolled_window.set_policy( + Gtk.PolicyType.ALWAYS, Gtk.PolicyType.ALWAYS) + self.file_scrolled_window.set_size_request(175, -1) + self.file_scrolled_window.hide() + self.file_scrolled_window.set_no_show_all(True) + + self.file_listview = Gtk.TreeView.new_with_model(self.file_liststore) + self.file_listview.set_headers_visible(False) + col = Gtk.TreeViewColumn(title=None) + self.file_listview.append_column(col) + cell = Gtk.CellRendererToggle() + col.pack_start(cell, True) + cell.set_property("activatable", True) + col.add_attribute(cell, "active", 1) + cell.connect("toggled", self.toggled_cb, self.file_liststore) + + col = Gtk.TreeViewColumn(title=None) + self.file_listview.append_column(col) + cell = Gtk.CellRendererText() + col.pack_start(cell, True) + col.add_attribute(cell, "text", 0) + + self.file_listview.show_all() + self.file_scrolled_window.add(self.file_listview) + vbox.pack_start(self.file_scrolled_window, False, True, 0) + + hbox = HIGHBox(False, 2) + self.remove_file_button = HIGButton(stock=Gtk.STOCK_REMOVE) + self.remove_file_button.connect( + "clicked", self.remove_file_button_clicked_cb) + self.remove_file_button.set_sensitive(False) + hbox.pack_end(self.remove_file_button, True, True, 0) + add_file_button = HIGButton(stock=Gtk.STOCK_ADD) + add_file_button.connect("clicked", self.add_file_button_clicked_cb) + hbox.pack_end(add_file_button, True, True, 0) + + vbox.pack_start(hbox, False, False, 0) + + return vbox + + def refresh_list_scripts(self, selected_scripts): + """The list of selected scripts is refreshed in the list store.""" + for row in self.liststore: + row[1] = False + for row in self.file_liststore: + row[1] = False + for filename in selected_scripts: + for row in self.liststore: + if row[0] == self.strip_file_name(os.path.basename(filename)): + row[1] = True + break + else: + for row in self.file_liststore: + if row[0] == filename: + row[1] = True + break + else: + self.file_liststore.append([filename, True]) + + def strip_file_name(self, filename): + """Removes a ".nse" extension from filename if present.""" + if(filename.endswith(".nse")): + return filename[:-4] + else: + return filename + + def set_script_from_selection(self): + scriptsname = [] + for entry in self.liststore: + if entry[1]: + scriptsname.append(self.strip_file_name(entry[0])) + for entry in self.file_liststore: + if entry[1]: + scriptsname.append(entry[0]) + if len(scriptsname) == 0: + self.ops["--script"] = None + else: + self.ops["--script"] = ",".join(scriptsname) + self.update_command() + + def toggled_cb(self, cell, path, model): + """Callback method, called when the check box in list of scripts is + toggled.""" + model[path][1] = not model[path][1] + self.set_script_from_selection() + + def make_description_widget(self): + """Creates and packs widgets related to displaying the description + box.""" + sw = HIGScrolledWindow() + sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.ALWAYS) + sw.set_shadow_type(Gtk.ShadowType.OUT) + sw.set_border_width(5) + text_view = Gtk.TextView() + text_view.connect("enter-notify-event", self.update_help_desc_cb) + self.text_buffer = text_view.get_buffer() + self.text_buffer.create_tag("Usage", font="Monospace") + self.text_buffer.create_tag("Output", font="Monospace") + text_view.set_wrap_mode(Gtk.WrapMode.WORD) + text_view.set_editable(False) + text_view.set_justification(Gtk.Justification.LEFT) + sw.add(text_view) + return sw + + def make_arguments_widget(self): + """Creates and packs widgets related to arguments box.""" + vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) + vbox.pack_start(Gtk.Label.new(_("Arguments")), False, False, 0) + arg_window = HIGScrolledWindow() + arg_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.ALWAYS) + arg_window.set_shadow_type(Gtk.ShadowType.OUT) + + arg_listview = Gtk.TreeView.new_with_model(self.arg_liststore) + arg_listview.connect("motion-notify-event", self.update_help_arg_cb) + argument = Gtk.CellRendererText() + self.value = Gtk.CellRendererText() + self.value.connect("edited", self.value_edited_cb, self.arg_liststore) + arg_col = Gtk.TreeViewColumn(title="Arguments\t") + val_col = Gtk.TreeViewColumn(title="values") + arg_listview.append_column(arg_col) + arg_listview.append_column(val_col) + arg_col.pack_start(argument, True) + arg_col.add_attribute(argument, "text", 0) + val_col.pack_start(self.value, True) + val_col.add_attribute(self.value, "text", 1) + + arg_window.add(arg_listview) + vbox.pack_start(arg_window, True, True, 0) + + return vbox + + def value_edited_cb(self, cell, path, new_text, model): + """Called when the argument cell is edited.""" + self.arg_list = [] + model[path][1] = new_text + argument_name = model[path][0] + self.arg_values[argument_name] = new_text + self.update_arg_values() + + def update_arg_values(self): + """When the widget is updated with argument value, correspondingly + update the command line.""" + for key in self.arg_values.keys(): + if len(self.arg_values[key]) == 0: + del self.arg_values[key] + else: + self.arg_list.append(key + "=" + self.arg_values[key]) + if len(self.arg_list) == 0: + self.ops["--script-args"] = None + self.arg_values.clear() + else: + self.ops["--script-args"] = ",".join(self.arg_list) + self.update_command() + + def selection_changed_cb(self, selection): + """Called back when the list of scripts is selected.""" + model, selection = selection.get_selected_rows() + for path in selection: + entry = model.get_value(model.get_iter(path), 2) + self.set_description(entry) + self.populate_arg_list(entry) + # Remember the currently pointing script entry + self.focusedentry = entry + + def update_help_ls_cb(self, widget, extra): # list of scripts + """Callback method to display the help for the list of scripts.""" + self.help_buf.set_text(self.list_scripts_help) + + def update_help_desc_cb(self, widget, extra): + """Callback method for displaying description.""" + self.help_buf.set_text(self.description_help) + + def update_help_arg_cb(self, treeview, event): + """Callback method for displaying argument help.""" + wx, wy = treeview.get_pointer() + x, y = treeview.convert_widget_to_bin_window_coords(wx, wy) + path = treeview.get_path_at_pos(x, y) + if not path or not self.focusedentry: + self.help_buf.set_text("") + return + path = path[0] + model, selected = treeview.get_selection().get_selected() + arg_name, arg_desc = model.get_value(model.get_iter(path), 2) + if arg_desc is not None: + self.help_buf.set_text("") + self.help_buf.insert( + self.help_buf.get_end_iter(), text="%s\n" % arg_name) + text_buffer_insert_nsedoc(self.help_buf, arg_desc) + else: + self.help_buf.set_text("") + + def add_file_button_clicked_cb(self, button): + if self.script_file_chooser is None: + self.script_file_chooser = \ + zenmapGUI.FileChoosers.ScriptFileChooserDialog( + title=_("Select script files")) + response = self.script_file_chooser.run() + filenames = self.script_file_chooser.get_filenames() + self.script_file_chooser.hide() + if response != Gtk.ResponseType.OK: + return + for filename in filenames: + self.file_liststore.append([filename, True]) + if len(self.file_liststore) > 0: + self.file_scrolled_window.show() + self.remove_file_button.set_sensitive(True) + self.set_script_from_selection() + + def remove_file_button_clicked_cb(self, button): + selection = self.file_listview.get_selection() + model, selection = selection.get_selected_rows() + for path in selection: + self.file_liststore.remove(model.get_iter(path)) + if len(self.file_liststore) == 0: + self.file_scrolled_window.hide() + self.remove_file_button.set_sensitive(False) + self.set_script_from_selection() + + def set_description(self, entry): + """Sets the content that is to be displayed in the description box.""" + self.text_buffer.set_text("") + + self.text_buffer.insert(self.text_buffer.get_end_iter(), """\ +Categories: %(cats)s +""" % {"cats": ", ".join(entry.categories)}) + text_buffer_insert_nsedoc(self.text_buffer, entry.description) + if entry.usage: + self.text_buffer.insert( + self.text_buffer.get_end_iter(), "\nUsage\n") + self.text_buffer.insert_with_tags_by_name( + self.text_buffer.get_end_iter(), entry.usage, "Usage") + if entry.output: + self.text_buffer.insert( + self.text_buffer.get_end_iter(), "\nOutput\n") + self.text_buffer.insert_with_tags_by_name( + self.text_buffer.get_end_iter(), entry.output, "Output") + if entry.url: + self.text_buffer.insert( + self.text_buffer.get_end_iter(), "\n" + entry.url) + + def populate_arg_list(self, entry): + """Called when a particular script is hovered over to display its + arguments and values (if any).""" + self.arg_liststore.clear() + self.current_arguments = [] + self.value.set_property('editable', True) + for arg in entry.arguments: + arg_name, arg_desc = arg + self.current_arguments.append(arg) + value = self.arg_values.get(arg_name) + if not value: + self.arg_liststore.append([arg_name, None, arg]) + else: + self.arg_liststore.append([arg_name, value, arg]) |