From 8a9d5a8506af41060471e7d69dbcdbcd4b31e5b3 Mon Sep 17 00:00:00 2001 From: Samuel Abels Date: Sun, 5 Mar 2017 12:19:50 +0100 Subject: [PATCH 1/5] remove TkExscript --- doc/mkapidoc.py | 4 +- setup.py | 4 +- src/TkExscript/LoginWidget.py | 132 -------------- src/TkExscript/LoginWindow.py | 76 -------- src/TkExscript/MailWidget.py | 219 ------------------------ src/TkExscript/MailWindow.py | 71 -------- src/TkExscript/Notebook.py | 95 ---------- src/TkExscript/ProgressBar.py | 57 ------ src/TkExscript/QueueWidget.py | 156 ----------------- src/TkExscript/QueueWindow.py | 33 ---- src/TkExscript/__init__.py | 29 ---- src/TkExscript/compat/__init__.py | 5 - src/TkExscript/compat/tkCommonDialog.py | 44 ----- src/TkExscript/compat/tkMessageBox.py | 96 ----------- tests/TkExscript/LoginWindowTest.py | 15 -- tests/TkExscript/MailWindowTest.py | 4 - tests/TkExscript/QueueWindowTest.py | 21 --- 17 files changed, 2 insertions(+), 1059 deletions(-) delete mode 100644 src/TkExscript/LoginWidget.py delete mode 100644 src/TkExscript/LoginWindow.py delete mode 100644 src/TkExscript/MailWidget.py delete mode 100644 src/TkExscript/MailWindow.py delete mode 100644 src/TkExscript/Notebook.py delete mode 100644 src/TkExscript/ProgressBar.py delete mode 100644 src/TkExscript/QueueWidget.py delete mode 100644 src/TkExscript/QueueWindow.py delete mode 100644 src/TkExscript/__init__.py delete mode 100644 src/TkExscript/compat/__init__.py delete mode 100644 src/TkExscript/compat/tkCommonDialog.py delete mode 100644 src/TkExscript/compat/tkMessageBox.py delete mode 100644 tests/TkExscript/LoginWindowTest.py delete mode 100644 tests/TkExscript/MailWindowTest.py delete mode 100644 tests/TkExscript/QueueWindowTest.py diff --git a/doc/mkapidoc.py b/doc/mkapidoc.py index 7065525c..45b00e20 100644 --- a/doc/mkapidoc.py +++ b/doc/mkapidoc.py @@ -40,7 +40,6 @@ r'--exclude ^Exscriptd\.daemonize$', r'--exclude ^Exscriptd\.util$', r'--exclude ^Exscriptd\.pidutil$', - r'--exclude ^TkExscript\.compat$', '--html', '--no-private', '--introspect-only', @@ -50,7 +49,6 @@ '-v', '-o %s' % doc_dir, os.path.join(base_dir, project), - os.path.join(base_dir, 'Exscriptd'), - os.path.join(base_dir, 'TkExscript')]) + os.path.join(base_dir, 'Exscriptd')]) print cmd os.system(cmd) diff --git a/setup.py b/setup.py index c476a79c..eeda4c0e 100644 --- a/setup.py +++ b/setup.py @@ -23,8 +23,7 @@ license = 'GPLv2', package_dir = {'': 'src', 'Exscript': os.path.join('src', 'Exscript'), - 'Exscriptd': os.path.join('src', 'Exscriptd'), - 'TkExscript': os.path.join('src', 'TkExscript')}, + 'Exscriptd': os.path.join('src', 'Exscriptd')}, package_data = {'Exscriptd': [ os.path.join('config', 'exscriptd.in'), os.path.join('config', 'main.xml.in'), @@ -37,7 +36,6 @@ install_requires = ['paramiko', 'pycrypto'], extras_require = { 'Exscriptd': ['sqlalchemy', 'lxml'], - 'TkExscript': ['tkinter'], }, keywords = ' '.join(['exscript', 'exscripd', diff --git a/src/TkExscript/LoginWidget.py b/src/TkExscript/LoginWidget.py deleted file mode 100644 index 47fce719..00000000 --- a/src/TkExscript/LoginWidget.py +++ /dev/null @@ -1,132 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" -A widget for entering username and password. -""" -from Tkinter import * -from Exscript import Account - -class LoginWidget(Frame): - """ - A widget that asks for username and password. - """ - - def __init__(self, parent, account = None, show_authorization = False): - """ - A simple login widget with a username and password field. - - @type parent: tkinter.Frame - @param parent: The parent widget. - @type account: Exscript.Account - @param account: An optional account that is edited. - @type show_authorization: bool - @param show_authorization: Whether to show the "Authorization" entry. - """ - Frame.__init__(self, parent) - self.pack(expand = True, fill = BOTH) - self.columnconfigure(0, pad = 6) - self.columnconfigure(1, weight = 1) - row = -1 - - # Username field. - self.label_user = Label(self, text = 'User:') - self.entry_user = Entry(self) - row += 1 - self.rowconfigure(row, pad = row > 0 and 6 or 0) - self.label_user.grid(row = row, column = 0, sticky = W) - self.entry_user.grid(row = row, column = 1, columnspan = 2, sticky = W+E) - self.entry_user.bind('', self._on_field_changed) - - # Password field. - self.label_password1 = Label(self, text = 'Password:') - self.entry_password1 = Entry(self, show = '*') - row += 1 - self.rowconfigure(row, pad = row > 0 and 6 or 0) - self.label_password1.grid(row = row, column = 0, sticky = W) - self.entry_password1.grid(row = row, column = 1, columnspan = 2, sticky = W+E) - self.entry_password1.bind('', self._on_field_changed) - - # Authorization password field. - self.label_password2 = Label(self, text = 'Authorization:') - self.entry_password2 = Entry(self, show = '*') - if show_authorization: - row += 1 - self.rowconfigure(row, pad = row > 0 and 6 or 0) - self.label_password2.grid(row = row, column = 0, sticky = W) - self.entry_password2.grid(row = row, column = 1, columnspan = 2, sticky = W+E) - self.entry_password2.bind('', self._on_field_changed) - - self.locked = False - self.account = None - self.attach(account and account or Account()) - - def _on_field_changed(self, event): - if self.locked: - return - # No idea if there is another way to receive a key event AFTER it - # has completed, so this hack works for now. - self.after(1, self._update_account) - - def _on_subject_changed(self, event): - if self.locked: - return - self._on_field_changed(event) - - def _update_account(self): - if self.locked: - return - self.locked = True - self.account.set_name(self.entry_user.get()) - self.account.set_password(self.entry_password1.get()) - self.account.set_authorization_password(self.entry_password2.get()) - self.locked = False - - def _account_changed(self, account): - if self.locked: - return - self.locked = True - self.entry_user.delete(0, END) - self.entry_user.insert(END, account.get_name()) - - self.entry_password1.delete(0, END) - self.entry_password1.insert(END, account.get_password()) - - self.entry_password2.delete(0, END) - self.entry_password2.insert(END, account.get_authorization_password()) - self.locked = False - - def attach(self, account): - """ - Attaches the given account to the widget, such that any changes - that are made in the widget are automatically reflected in the - given account. - - @type account: Exscript.Account - @param account: The account object to attach. - """ - if self.account: - self.account.changed_event.disconnect(self._account_changed) - self.account = account - self.account.changed_event.connect(self._account_changed) - self._account_changed(account) - - def get_account(self): - """ - Returns the attached account object. - - @rtype: Exscript.Account - @return: The account that is currently edited. - """ - return self.account diff --git a/src/TkExscript/LoginWindow.py b/src/TkExscript/LoginWindow.py deleted file mode 100644 index 3824b938..00000000 --- a/src/TkExscript/LoginWindow.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" -A window containing a LoginWidget. -""" -from Tkinter import * -from TkExscript.LoginWidget import LoginWidget - -class _ButtonBar(Frame): - def __init__(self, parent, on_cancel = None, on_start = None, **kwargs): - Frame.__init__(self, parent) - self.pack(expand = True, fill = BOTH) - self.columnconfigure(0, weight = 1) - self.columnconfigure(1, pad = 12) - self.columnconfigure(2, pad = 12) - - send = Button(self, text = 'Start', command = on_start) - send.grid(row = 0, column = 2, sticky = E) - send = Button(self, text = 'Cancel', command = on_cancel) - send.grid(row = 0, column = 1, sticky = E) - -class LoginWindow(Frame): - """ - A simple TkFrame that shows a LoginWidget. - This class supports all of the same methods that LoginWidget supports; - any calls are proxied directly to the underlying widget. - """ - - def __init__(self, - account = None, - show_authorization = False, - on_start = None): - """ - Create a new login window. All arguments are passed to the - underlying LoginWidget. - - @type account: Exscript.Account - @param account: An optional account that is edited. - @type show_authorization: bool - @param show_authorization: Whether to show the "Authorization" entry. - @type on_start: function - @param on_start: Called when the start button is clicked. - """ - self.widget = None - Frame.__init__(self) - self.pack(expand = True, fill = BOTH) - - self.widget = LoginWidget(self, - account, - show_authorization = show_authorization) - self.widget.pack(expand = True, fill = BOTH, padx = 6, pady = 6) - - self.buttons = _ButtonBar(self, - on_cancel = self.quit, - on_start = self._on_start) - self.buttons.pack(expand = False, fill = X, padx = 6, pady = 3) - - self._on_start_cb = on_start - - def __getattr__(self, name): - return getattr(self.widget, name) - - def _on_start(self): - self._on_start_cb(self) diff --git a/src/TkExscript/MailWidget.py b/src/TkExscript/MailWidget.py deleted file mode 100644 index 173d787c..00000000 --- a/src/TkExscript/MailWidget.py +++ /dev/null @@ -1,219 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" -A simple email editor. -""" -from Tkinter import * -from Exscript.util.mail import Mail, send -try: - import tkMessageBox -except ImportError: - from TkExscript.compat import tkMessageBox - -class _ButtonBar(Frame): - def __init__(self, parent, on_cancel = None, on_send = None, **kwargs): - Frame.__init__(self, parent) - self.pack(expand = True, fill = BOTH) - self.columnconfigure(0, weight = 1) - self.columnconfigure(1, pad = 12) - self.columnconfigure(2, pad = 12) - - send = Button(self, text = 'Send', command = on_send) - send.grid(row = 0, column = 2, sticky = E) - send = Button(self, text = 'Cancel', command = on_cancel) - send.grid(row = 0, column = 1, sticky = E) - -class MailWidget(Frame): - """ - A widget for editing and sending a mail. - """ - - def __init__(self, - parent, - mail = None, - server = 'localhost', - show_to = True, - show_cc = True, - show_bcc = False, - on_subject_changed = None): - """ - A simple editor for sending emails. If the given mail is None, a - new mail is created, else it is passed to attach(). - - @type parent: tkinter.Frame - @param parent: The parent widget. - @type mail: Exscript.util.mail.Mail - @param mail: The email object to attach. - @type server: string - @param server: The address of the mailserver. - @type show_to: bool - @param show_to: Whether to show the "To:" entry box. - @type show_cc: bool - @param show_cc: Whether to show the "Cc:" entry box. - @type show_bcc: bool - @param show_bcc: Whether to show the "Bcc:" entry box. - @type on_subject_changed: function - @param on_subject_changed: Called whenever the subject changes. - """ - Frame.__init__(self, parent) - self.pack(expand = True, fill = BOTH) - self.columnconfigure(0, pad = 6) - self.columnconfigure(1, weight = 1) - - row = -1 - self.label_to = Label(self, text = 'To:') - self.entry_to = Entry(self) - if show_to: - row += 1 - self.rowconfigure(row, pad = row > 0 and 6 or 0) - self.label_to.grid(row = row, column = 0, sticky = W) - self.entry_to.grid(row = row, column = 1, columnspan = 2, sticky = W+E) - self.entry_to.bind('', self._on_field_changed) - - self.label_cc = Label(self, text = 'Cc:') - self.entry_cc = Entry(self) - if show_cc: - row += 1 - self.rowconfigure(row, pad = row > 0 and 6 or 0) - self.label_cc.grid(row = row, column = 0, sticky = W) - self.entry_cc.grid(row = row, column = 1, columnspan = 2, sticky = W+E) - self.entry_cc.bind('', self._on_field_changed) - - self.label_bcc = Label(self, text = 'Bcc:') - self.entry_bcc = Entry(self) - if show_bcc: - row += 1 - self.rowconfigure(row, pad = row > 0 and 6 or 0) - self.label_bcc.grid(row = row, column = 0, sticky = W) - self.entry_bcc.grid(row = row, column = 1, columnspan = 2, sticky = W+E) - self.entry_bcc.bind('', self._on_field_changed) - - row += 1 - self.rowconfigure(row, pad = row > 0 and 6 or 0) - self.label_subject = Label(self, text = 'Subject:') - self.label_subject.grid(row = row, column = 0, sticky = W) - self.entry_subject = Entry(self) - self.entry_subject.grid(row = row, column = 1, columnspan = 2, sticky = W+E) - self.entry_subject.bind('', self._on_subject_changed) - - row += 1 - self.rowconfigure(row, pad = 6, weight = 1) - scrollbar = Scrollbar(self, takefocus = 0) - scrollbar.grid(row = row, column = 2, sticky = N+S) - self.text_widget = Text(self) - self.text_widget.grid(row = row, - column = 0, - columnspan = 2, - sticky = N+S+E+W) - self.text_widget.config(yscrollcommand = scrollbar.set) - self.text_widget.bind('', self._on_field_changed) - scrollbar.config(command = self.text_widget.yview) - - row += 1 - self.rowconfigure(row, pad = 6) - self.buttons = _ButtonBar(self, - on_cancel = parent.quit, - on_send = self._on_send) - self.buttons.grid(row = row, column = 0, columnspan = 3, sticky = E) - - self.server = server - self.on_subject_changed_cb = on_subject_changed - self.locked = False - self.mail = None - self.attach(mail and mail or Mail()) - - def _on_field_changed(self, event): - if self.locked: - return - # No idea if there is another way to receive a key event AFTER it - # has completed, so this hack works for now. - self.after(1, self._update_mail) - - def _on_subject_changed(self, event): - if self.locked: - return - self._on_field_changed(event) - # No idea if there is another way to receive a key event AFTER it - # has completed, so this hack works for now. - if self.on_subject_changed_cb: - self.after(1, self.on_subject_changed_cb) - - def _update_mail(self): - if self.locked: - return - self.locked = True - self.mail.set_to(self.entry_to.get()) - self.mail.set_cc(self.entry_cc.get()) - self.mail.set_bcc(self.entry_bcc.get()) - self.mail.set_subject(self.entry_subject.get()) - self.mail.set_body(self.text_widget.get('0.0', END)) - self.locked = False - - def _update_ui(self): - if self.locked: - return - self.locked = True - self.entry_to.delete(0, END) - self.entry_to.insert(END, ', '.join(self.mail.get_to())) - - self.entry_cc.delete(0, END) - self.entry_cc.insert(END, ', '.join(self.mail.get_cc())) - - self.entry_bcc.delete(0, END) - self.entry_bcc.insert(END, ', '.join(self.mail.get_bcc())) - - self.entry_subject.delete(0, END) - self.entry_subject.insert(END, self.mail.get_subject()) - - self.text_widget.delete('0.0', END) - self.text_widget.insert(END, self.mail.get_body()) - self.locked = False - - def attach(self, mail): - """ - Attaches the given email to the editor, such that any changes - that are made in the editor are automatically reflected in the - given email. - - @type mail: Exscript.util.mail.Mail - @param mail: The email object to attach. - """ - if self.mail: - self.mail.changed_event.disconnect(self._update_ui) - self.mail = mail - self.mail.changed_event.connect(self._update_ui) - self._update_ui() - - def get_mail(self): - """ - Returns the attached email object. - - @rtype: Exscript.util.mail.Mail - @return: The mail that is currently edited. - """ - return self.mail - - def _on_send(self): - try: - send(self.mail, server = self.server) - except Exception, e: - title = 'Send failed' - message = 'The email could not be sent using %s.' % self.server - message += ' This was the error:\n' - message += str(e) - if tkMessageBox.askretrycancel(title, message): - self.after(1, self._on_send) - return - self.quit() diff --git a/src/TkExscript/MailWindow.py b/src/TkExscript/MailWindow.py deleted file mode 100644 index 50101b29..00000000 --- a/src/TkExscript/MailWindow.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" -A window containing a MailWidget. -""" -from Tkinter import * -from TkExscript.MailWidget import MailWidget - -class MailWindow(Frame): - """ - A simple TkFrame that shows a MailWidget. - This class supports all of the same methods that MailWidget supports; - any calls are proxied directly to the underlying widget. - """ - - def __init__(self, - mail = None, - server = 'localhost', - show_to = True, - show_cc = True, - show_bcc = False): - """ - Create a new editor window. All arguments are passed to the - underlying MailWidget. - - @type mail: Exscript.util.mail.Mail - @param mail: An optional email object to attach. - @type server: string - @param server: The address of the mailserver. - @type show_to: bool - @param show_to: Whether to show the "To:" entry box. - @type show_cc: bool - @param show_cc: Whether to show the "Cc:" entry box. - @type show_bcc: bool - @param show_bcc: Whether to show the "Bcc:" entry box. - """ - self.widget = None - Frame.__init__(self) - self.pack(expand = True, fill = BOTH) - - self.widget = MailWidget(self, - mail, - server = server, - show_to = show_to, - show_cc = show_cc, - show_bcc = show_bcc, - on_subject_changed = self._update_subject) - self.widget.pack(expand = True, fill = BOTH, padx = 6, pady = 6) - self._on_subject_changed(None) - - def _update_subject(self): - subject = self.widget.get_mail().get_subject() - if subject: - self.master.title(subject) - else: - self.master.title('Send a mail') - - def __getattr__(self, name): - return getattr(self.widget, name) diff --git a/src/TkExscript/Notebook.py b/src/TkExscript/Notebook.py deleted file mode 100644 index 34e376b2..00000000 --- a/src/TkExscript/Notebook.py +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" -A notebook widget. -""" -import Tkinter as tk - -class Notebook(tk.Frame): - """ - A notebook widget for Tkinter applications. - """ - def __init__(self, parent): - tk.Frame.__init__(self, parent) - self.active_page = None - self.new_tab_side = tk.LEFT - self.tabgroup = tk.IntVar(0) - self.tabs = [] - self.tab_buttons = [] - self.tab_area = tk.Frame(self) - self.count = 0 - self.tab_area.pack(fill = tk.BOTH, side = tk.TOP) - - def _display_page(self, pg): - """ - Shows the selected page, hides former page - """ - if self.active_page: - self.active_page.forget() - pg.pack(fill = tk.BOTH, expand = True) - self.active_page = pg - - def append_page(self, title): - """ - Adds a new page to the notebook and returns it. - """ - self.count += 1 - pos = len(self.tabs) - page = tk.Frame(self) - button = tk.Radiobutton(self.tab_area, - text = title, - indicatoron = False, - variable = self.tabgroup, - value = self.count, - relief = tk.RIDGE, - offrelief = tk.RIDGE, - borderwidth = 1, - command = lambda: self._display_page(page)) - button.pack(fill = tk.BOTH, - side = self.new_tab_side, - padx = 0, - pady = 0) - self.tabs.append(page) - self.tab_buttons.append(button) - if self.active_page is None: - self.select(pos) - return page - - def remove_page(self, page): - """ - Removes the given page from the notebook. - """ - pageno = self.tabs.index(page) - button = self.tab_buttons[pageno] - page.forget() - button.forget() - self.tabs.remove(page) - self.tab_buttons.remove(button) - if self.tabs: - newpage = min(pageno, len(self.tabs) - 1) - self.select(min(pageno, len(self.tabs) - 1)) - - def select_page(self, page): - """ - Selects the given page. - """ - self.select(self.tabs.index(page)) - - def select(self, page_number): - """ - Selects the page with the given number. - """ - self.tab_buttons[page_number].select() - self._display_page(self.tabs[page_number]) diff --git a/src/TkExscript/ProgressBar.py b/src/TkExscript/ProgressBar.py deleted file mode 100644 index b3a477e6..00000000 --- a/src/TkExscript/ProgressBar.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" -A progress bar widget. -""" -from Tkinter import * - -class ProgressBar(Frame): - ''' - A simple progress bar widget. - ''' - - def __init__(self, - parent, - fillcolor = 'orchid1', - text = '', - height = 20, - value = 0.0): - Frame.__init__(self, parent, bg = 'white', height = height) - self.pack(expand = True, fill = BOTH) - self.canvas = Canvas(self, - bg = self['bg'], - height = self['height'], - highlightthickness = 0, - relief = 'flat', - bd = 0) - self.canvas.pack(fill = BOTH, expand = True) - self.rect = self.canvas.create_rectangle(0, 0, 0, 0, fill = fillcolor, outline = '') - self.text = self.canvas.create_text(0, 0, text='') - self.set(value, text) - - def set(self, value = 0.0, text = None): - value = max(value, 0.0) - value = min(value, 1.0) - - # Update the progress bar. - height = self.canvas.winfo_height() - width = self.canvas.winfo_width() - self.canvas.coords(self.rect, 0, 0, width * value, height) - - # Update the text. - if text == None: - text = str(int(round(100 * value))) + ' %' - self.canvas.coords(self.text, width / 2, height / 2) - self.canvas.itemconfigure(self.text, text = text) diff --git a/src/TkExscript/QueueWidget.py b/src/TkExscript/QueueWidget.py deleted file mode 100644 index e104d60c..00000000 --- a/src/TkExscript/QueueWidget.py +++ /dev/null @@ -1,156 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" -A simple email editor. -""" -import Queue -from Tkinter import * -from TkExscript.Notebook import Notebook -from TkExscript.ProgressBar import ProgressBar - -class _ConnectionWatcherWidget(Frame): - def __init__(self, parent): - Frame.__init__(self, parent) - scrollbar = Scrollbar(parent, takefocus = 0) - scrollbar.pack(fill = BOTH, side = RIGHT) - self.text_widget = Text(parent) - self.text_widget.pack(fill='both', expand=True) - self.text_widget.config(yscrollcommand = scrollbar.set) - scrollbar.config(command = self.text_widget.yview) - - self.data_queue = Queue.Queue() - self._update() - - def _update(self): - try: - while True: - func, args = self.data_queue.get_nowait() - func(*args) - self.update_idletasks() - except Queue.Empty: - pass - self.text_widget.see(END) - self.after(100, self._update) - -class _ConnectionWatcher(object): - def __init__(self, conn): - self.buffer = '' - self.widget = None - self.conn = conn - self.conn.data_received_event.connect(self._on_data_received) - - def _show_data(self, data): - data = data.replace('\r\n', '\n') - func = self.widget.text_widget.insert - self.widget.data_queue.put((func, (END, data))) - - def create_widget(self, parent): - self.widget = _ConnectionWatcherWidget(parent) - self._show_data(self.buffer) - self.buffer = '' - - def _on_data_received(self, data): - if self.widget: - self._show_data(data) - else: - self.buffer += data - -class QueueWidget(Frame): - """ - A widget for watching Exscript.Queue. - """ - - def __init__(self, parent, queue): - """ - Create the widget. - - @type parent: tkinter.Frame - @param parent: The parent widget. - @type queue: Exscript.Queue - @param queue: The watched queue. - """ - Frame.__init__(self, parent) - self.pack(expand = True, fill = BOTH) - self.columnconfigure(0, pad = 6) - self.columnconfigure(1, weight = 1) - row = -1 - - # Progress bar. - row += 1 - self.rowconfigure(row, weight = 0) - self.label_progress = Label(self, text = 'Progress:') - self.progress_bar = ProgressBar(self) - self.label_progress.grid(row = row, column = 0, sticky = W) - self.progress_bar.grid(row = row, column = 1, sticky = W+E) - - # Padding. - row += 1 - self.rowconfigure(row, pad = 6) - padding = Frame(self) - padding.grid(row = row, column = 0, sticky = W) - - row += 1 - self.rowconfigure(row, weight = 1) - self.notebook = Notebook(self) - self.notebook.grid(row = row, - column = 0, - columnspan = 2, - sticky = N+S+E+W) - - self.data_queue = Queue.Queue() - self.pages = {} - self.queue = queue - self.queue.workqueue.job_started_event.connect(self._on_job_started) - self.queue.workqueue.job_error_event.connect(self._on_job_error) - self.queue.workqueue.job_succeeded_event.connect(self._on_job_succeeded) - self.queue.workqueue.job_aborted_event.connect(self._on_job_aborted) - self._update() - - def _update_progress(self): - self.progress_bar.set(self.queue.get_progress() / 100) - - def _create_page(self, action, watcher): - page = self.notebook.append_page(action.get_name()) - self.pages[action] = page - watcher.create_widget(page) - - def _remove_page(self, action): - page = self.pages[action] - del self.pages[action] - self.notebook.remove_page(page) - - def _update(self): - try: - while True: - func, args = self.data_queue.get_nowait() - func(*args) - self.update_idletasks() - except Queue.Empty: - pass - self._update_progress() - self.after(100, self._update) - - def _on_job_started(self, job): - watcher = _ConnectionWatcher(conn) - self.data_queue.put((self._create_page, (job, watcher))) - - def _on_job_error(self, job, e): - self.data_queue.put((self._remove_page, (job,))) - - def _on_job_succeeded(self, job): - self.data_queue.put((self._remove_page, (job,))) - - def _on_job_aborted(self, job): - pass diff --git a/src/TkExscript/QueueWindow.py b/src/TkExscript/QueueWindow.py deleted file mode 100644 index 1e5eb3be..00000000 --- a/src/TkExscript/QueueWindow.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" -A window containing a MailWatcher. -""" -from Tkinter import * -from TkExscript.QueueWidget import QueueWidget - -class QueueWindow(Frame): - def __init__(self, queue, **kwargs): - self.widget = None - Frame.__init__(self) - self.pack(expand = True, fill = BOTH) - self.widget = QueueWidget(self, queue) - self.widget.pack(expand = True, fill = BOTH, padx = 6, pady = 6) - - if kwargs.get('autoclose', False): - queue.queue_empty_event.connect(self._on_queue_empty) - - def _on_queue_empty(self): - self.after(1, self.quit) diff --git a/src/TkExscript/__init__.py b/src/TkExscript/__init__.py deleted file mode 100644 index 4ec31807..00000000 --- a/src/TkExscript/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" -Contains graphical user interfaces using Python's tkinter. -""" -from TkExscript.LoginWidget import LoginWidget -from TkExscript.LoginWindow import LoginWindow -from TkExscript.MailWidget import MailWidget -from TkExscript.MailWindow import MailWindow -from TkExscript.Notebook import Notebook -from TkExscript.ProgressBar import ProgressBar -from TkExscript.QueueWidget import QueueWidget -from TkExscript.QueueWindow import QueueWindow - -import inspect -__all__ = [name for name, obj in locals().items() - if not (name.startswith('_') or inspect.ismodule(obj))] diff --git a/src/TkExscript/compat/__init__.py b/src/TkExscript/compat/__init__.py deleted file mode 100644 index 6da2a604..00000000 --- a/src/TkExscript/compat/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -import TkExscript.compat.tkMessageBox - -import inspect -__all__ = [name for name, obj in locals().items() - if not (name.startswith('_') or inspect.ismodule(obj))] diff --git a/src/TkExscript/compat/tkCommonDialog.py b/src/TkExscript/compat/tkCommonDialog.py deleted file mode 100644 index 2830e759..00000000 --- a/src/TkExscript/compat/tkCommonDialog.py +++ /dev/null @@ -1,44 +0,0 @@ -# -# Instant Python -# tkCommonDialog.py,v 1.2 1997/08/14 14:17:26 guido Exp -# -# base class for tk common dialogues -# -# this module provides a base class for accessing the common -# dialogues available in Tk 4.2 and newer. use tkFileDialog, -# tkColorChooser, and tkMessageBox to access the individual -# dialogs. -# -# written by Fredrik Lundh, May 1997 -# -from Tkinter import * -class Dialog: - command = None - def __init__(self, master=None, **options): - # FIXME: should this be placed on the module level instead? - if TkVersion < 4.2: - raise TclError, "this module requires Tk 4.2 or newer" - self.master = master - self.options = options - def _fixoptions(self): - pass # hook - def _fixresult(self, widget, result): - return result # hook - def show(self, **options): - # update instance options - for k, v in options.items(): - self.options[k] = v - self._fixoptions() - # we need a dummy widget to properly process the options - # (at least as long as we use Tkinter 1.63) - w = Frame(self.master) - try: - s = apply(w.tk.call, (self.command,) + w._options(self.options)) - s = self._fixresult(w, s) - finally: - try: - # get rid of the widget - w.destroy() - except: - pass - return s diff --git a/src/TkExscript/compat/tkMessageBox.py b/src/TkExscript/compat/tkMessageBox.py deleted file mode 100644 index c75ec527..00000000 --- a/src/TkExscript/compat/tkMessageBox.py +++ /dev/null @@ -1,96 +0,0 @@ -# -# Instant Python -# tkMessageBox.py,v 1.1 1997/07/19 20:02:36 guido Exp -# -# tk common message boxes -# -# this module provides an interface to the native message boxes -# available in Tk 4.2 and newer. -# -# written by Fredrik Lundh, May 1997 -# -# -# options (all have default values): -# -# - default: which button to make default (one of the reply codes) -# -# - icon: which icon to display (see below) -# -# - message: the message to display -# -# - parent: which window to place the dialog on top of -# -# - title: dialog title -# -# - type: dialog type; that is, which buttons to display (see below) -# -from TkExscript.compat.tkCommonDialog import Dialog -# -# constants -# icons -ERROR = "error" -INFO = "info" -QUESTION = "question" -WARNING = "warning" -# types -ABORTRETRYIGNORE = "abortretryignore" -OK = "ok" -OKCANCEL = "okcancel" -RETRYCANCEL = "retrycancel" -YESNO = "yesno" -YESNOCANCEL = "yesnocancel" -# replies -ABORT = "abort" -RETRY = "retry" -IGNORE = "ignore" -OK = "ok" -CANCEL = "cancel" -YES = "yes" -NO = "no" -# -# message dialog class -class Message(Dialog): - "A message box" - command = "tk_messageBox" -# -# convenience stuff -def _show(title=None, message=None, icon=None, type=None, **options): - if icon: options["icon"] = icon - if type: options["type"] = type - if title: options["title"] = title - if message: options["message"] = message - return apply(Message, (), options).show() -def showinfo(title=None, message=None, **options): - "Show an info message" - return apply(_show, (title, message, INFO, OK), options) -def showwarning(title=None, message=None, **options): - "Show a warning message" - return apply(_show, (title, message, WARNING, OK), options) -def showerror(title=None, message=None, **options): - "Show an error message" - return apply(_show, (title, message, ERROR, OK), options) -def askquestion(title=None, message=None, **options): - "Ask a question" - return apply(_show, (title, message, QUESTION, YESNO), options) -def askokcancel(title=None, message=None, **options): - "Ask if operation should proceed; return true if the answer is ok" - s = apply(_show, (title, message, QUESTION, OKCANCEL), options) - return s == OK -def askyesno(title=None, message=None, **options): - "Ask a question; return true if the answer is yes" - s = apply(_show, (title, message, QUESTION, YESNO), options) - return s == YES -def askretrycancel(title=None, message=None, **options): - "Ask if operation should be retried; return true if the answer is yes" - s = apply(_show, (title, message, WARNING, RETRYCANCEL), options) - return s == RETRY -# -------------------------------------------------------------------- -# test stuff -if __name__ == "__main__": - print "info", showinfo("Spam", "Egg Information") - print "warning", showwarning("Spam", "Egg Warning") - print "error", showerror("Spam", "Egg Alert") - print "question", askquestion("Spam", "Question?") - print "proceed", askokcancel("Spam", "Proceed?") - print "yes/no", askyesno("Spam", "Got it?") - print "try again", askretrycancel("Spam", "Try again?") diff --git a/tests/TkExscript/LoginWindowTest.py b/tests/TkExscript/LoginWindowTest.py deleted file mode 100644 index 7f1b763c..00000000 --- a/tests/TkExscript/LoginWindowTest.py +++ /dev/null @@ -1,15 +0,0 @@ -from getpass import getuser -from Exscript import Account -from TkExscript import LoginWindow - -def on_start(window): - account = window.get_account() - print "Username:", account.get_name() - print "Password:", account.get_password() - print "Authorization:", account.get_authorization_password() - window.quit() - -account = Account(getuser()) -LoginWindow(account, - show_authorization = True, - on_start = on_start).mainloop() diff --git a/tests/TkExscript/MailWindowTest.py b/tests/TkExscript/MailWindowTest.py deleted file mode 100644 index eee0ed41..00000000 --- a/tests/TkExscript/MailWindowTest.py +++ /dev/null @@ -1,4 +0,0 @@ -from Exscript.util.mail import Mail -from TkExscript import MailWindow -mail = Mail(subject = 'Test me', body = 'hello world') -MailWindow(mail).mainloop() diff --git a/tests/TkExscript/QueueWindowTest.py b/tests/TkExscript/QueueWindowTest.py deleted file mode 100644 index 322017e4..00000000 --- a/tests/TkExscript/QueueWindowTest.py +++ /dev/null @@ -1,21 +0,0 @@ -import time, Exscript.util.sigintcatcher -from Exscript import Queue, Account -from Exscript.util.decorator import bind -from TkExscript import QueueWindow - -def do_something(conn, wait): - conn.connect() - conn.authenticate() - for i in range(100): - conn.execute('test%d' % i) - time.sleep(wait) - conn.close() - -queue = Queue(max_threads = 4, verbose = 0) -queue.add_account(Account('test', 'test')) -window = QueueWindow(queue) -queue.run('dummy://dummy1', bind(do_something, .02)) -queue.run('dummy://dummy2', bind(do_something, .2)) -queue.run('dummy://dummy3', bind(do_something, .3)) -window.mainloop() -queue.shutdown() From a712d3ac1825a8c6bc7f409e2b964db08c355bc7 Mon Sep 17 00:00:00 2001 From: Samuel Abels Date: Sun, 5 Mar 2017 12:48:17 +0100 Subject: [PATCH 2/5] remove Exscriptd --- MANIFEST.in | 1 - TODO | 8 - doc/exscript-architecture.pdf | Bin 52825 -> 0 bytes doc/exscript-architecture.svg | 1647 --------------------- doc/exscript.en.tex | 111 -- doc/mkapidoc.py | 17 +- exclaim | 281 ---- exscriptd | 137 -- exscriptd-config | 121 -- setup.py | 19 +- src/Exscriptd/Client.py | 291 ---- src/Exscriptd/Config.py | 452 ------ src/Exscriptd/ConfigReader.py | 83 -- src/Exscriptd/DBObject.py | 63 - src/Exscriptd/Dispatcher.py | 250 ---- src/Exscriptd/HTTPDaemon.py | 242 --- src/Exscriptd/Order.py | 326 ---- src/Exscriptd/OrderDB.py | 579 -------- src/Exscriptd/Service.py | 81 - src/Exscriptd/Task.py | 328 ---- src/Exscriptd/__init__.py | 21 - src/Exscriptd/config/AccountPoolConfig.py | 57 - src/Exscriptd/config/BaseConfig.py | 129 -- src/Exscriptd/config/ConfigSection.py | 43 - src/Exscriptd/config/DaemonConfig.py | 87 -- src/Exscriptd/config/DatabaseConfig.py | 54 - src/Exscriptd/config/QueueConfig.py | 69 - src/Exscriptd/config/ServiceConfig.py | 106 -- src/Exscriptd/config/__init__.py | 15 - src/Exscriptd/config/exscriptd.in | 155 -- src/Exscriptd/config/main.xml.in | 81 - src/Exscriptd/util.py | 64 - src/Exscriptd/xml.py | 377 ----- tests/Exscriptd/OrderDBTest.py | 262 ---- tests/Exscriptd/pythonservice/order.xml | 6 - tests/Exscriptd/pythonservice/test.py | 20 - tests/Exscriptd/run_suite.py | 1 - 37 files changed, 5 insertions(+), 6579 deletions(-) delete mode 100644 doc/exscript-architecture.pdf delete mode 100644 doc/exscript-architecture.svg delete mode 100755 exclaim delete mode 100755 exscriptd delete mode 100755 exscriptd-config delete mode 100644 src/Exscriptd/Client.py delete mode 100644 src/Exscriptd/Config.py delete mode 100644 src/Exscriptd/ConfigReader.py delete mode 100644 src/Exscriptd/DBObject.py delete mode 100644 src/Exscriptd/Dispatcher.py delete mode 100644 src/Exscriptd/HTTPDaemon.py delete mode 100644 src/Exscriptd/Order.py delete mode 100644 src/Exscriptd/OrderDB.py delete mode 100644 src/Exscriptd/Service.py delete mode 100644 src/Exscriptd/Task.py delete mode 100644 src/Exscriptd/__init__.py delete mode 100644 src/Exscriptd/config/AccountPoolConfig.py delete mode 100644 src/Exscriptd/config/BaseConfig.py delete mode 100644 src/Exscriptd/config/ConfigSection.py delete mode 100644 src/Exscriptd/config/DaemonConfig.py delete mode 100644 src/Exscriptd/config/DatabaseConfig.py delete mode 100644 src/Exscriptd/config/QueueConfig.py delete mode 100644 src/Exscriptd/config/ServiceConfig.py delete mode 100644 src/Exscriptd/config/__init__.py delete mode 100755 src/Exscriptd/config/exscriptd.in delete mode 100644 src/Exscriptd/config/main.xml.in delete mode 100644 src/Exscriptd/util.py delete mode 100644 src/Exscriptd/xml.py delete mode 100644 tests/Exscriptd/OrderDBTest.py delete mode 100644 tests/Exscriptd/pythonservice/order.xml delete mode 100644 tests/Exscriptd/pythonservice/test.py delete mode 120000 tests/Exscriptd/run_suite.py diff --git a/MANIFEST.in b/MANIFEST.in index 6b10671a..bb3ec5f0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1 @@ -include src/Exscriptd/config/*.in include README.md diff --git a/TODO b/TODO index 1a10b90c..4c0256c8 100644 --- a/TODO +++ b/TODO @@ -1,13 +1,5 @@ -Exscript --------- * Add tests for Transport class debug levels. * Add API docs for Exscript.parselib. * Test the exscript executable. * Test Queue constructor kwargs. * Create more stdlib tests like tests/templates/stdlib.crypt/. - -exscriptd ---------- -* Make daemons asynchronous. -* Debug memory usage. -* Add more tests. diff --git a/doc/exscript-architecture.pdf b/doc/exscript-architecture.pdf deleted file mode 100644 index 3d8aa2d87201334364a3ad1a791f1ac1a3fb62c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52825 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%0Av&`-d3jcew!5+%~ zt!4Jt-7g#_|J~*S1MVx<&Jcn?V1Ug40gNqRNdwjy18m;Eqc*@!I{yQ0@GmF}#ijv4 z00i~#H-n*mH(BLB2BzXxK>ZqXfh=0A6S9+z3B>@(zSvwZV5A@b&kum8gy2Bl1_T3S z{r-il{kKP*aZLFYYz?(nxBmd1{^z*4H#Yj`rk8)sO&A7nJi%Zjuqf}ZGvg6|F*5$E z=|8v?fxkFTfFXgx@m&~$f&vMHK<*$I2oWNo!0hsO^pJ21EPs)- zPzVy61iqt#0RIRHU{5%$11vfKkW;`l0urSFVYo)Lcnp(CJvB7 zf`JeQkno2FG&_Kl;1Fr3FkmME2K`@1*Z)?q1h?|{*9iN2rX~zPOJPt9b~*6h*wVx; zqyBZm0>Cp0SRVrT_6QUTvmN*YfK*{1;SdR28i@G`LjV-&F9rDiTXn%++vHCg2LDPI zpaFOf$c_J-PG0|hw{wp!_#691xN-@yhfq?!^mF)i@?1x4A zv5{(E=@}4Dg+ov{H(dw_q5{D{6tF%STdf2J2Uf@Z4Mi5a^7mucauf9z`H9WLOKR&XfP?mAwa)wW$gW4qi8_DXP|y8vmo z@fd9l7gGnIwi61CyS2`byJswIfc5NquA&C4+jrTs|82GWHl_!x!~wL@k00y`PVkOq z|6XJVyEy)b=LEzGCt29%+$k%x!w-;%2XI8%o(%Le<#CU^LwN{fm-0Zu&QBtW{)XcG z-xELMCK2sAQ^BI$>l6Tj3IQX&QT%%n=DbLL&iB z|F46gfzlE{VjynCDPSsK-32rnDCYna$lH4W3)}9jYsYm3U?K4NjoKMI2X?6p!a
u-S^hGKw87xVypvic4%1 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%0D8PmfmG6htd{W|&YO!PiHO4Nt{K6*?R@AEt;; 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - Exscript - Architecture - - Queue - - - - - - - - - - - - - - - - - - - - - - - - - Logs - - watches - - Logger - Telnet,SSH - Telnet,SSH - Telnet,SSH - Telnet,SSH - - TemplateProcessor - - TemplateProcessor - - - - - writes - - - - - - - - - - OrderDatabase(SQLite) - - Exscriptd - - - Service - - - Service Calls - loads - - Service - - loads - - Service - - loads - - Startscript - - Service Call - Cron - - triggers - - diff --git a/doc/exscript.en.tex b/doc/exscript.en.tex index 75f9b8af..e15f28cd 100644 --- a/doc/exscript.en.tex +++ b/doc/exscript.en.tex @@ -991,117 +991,6 @@ \subsection{\product.Queue} documentation. -\newpage -\section{The Exscript Server} -\subsection{Overview} - -The \product daemon is a server that takes an order and runs services -asynchronously. It may, for example, be used as a backend for running -\product through a web server. - -Prerequisite: \product is already installed and working. - -The \product daemon is built provides services to an external caller, -and runs one or more instances of Exscript internally. Here is a -general overview: - -\includegraphics{exscript-architecture} - - -\subsection{Installation} - -The following instructions should work on most Debian based Linux -distributions. On other systems the procedure will probably differ. - -\begin{enumerate} -\item Define the directories for the following steps: - -\begin{verbatim} - export EXSCRIPT_LOG=/var/log/exscriptd - export EXSCRIPT_SPOOL=/var/spool/exscriptd - export EXSCRIPT_CFG=/etc/exscriptd - export EXSCRIPT_USER=exscriptd - export EXSCRIPT_GROUP=exscriptd -\end{verbatim} - -\item Create a user and group for the daemon: - -\begin{verbatim} - sudo adduser $EXSCRIPT_USER $EXSCRIPT_GROUP -\end{verbatim} - -\item Create the required directories: - -\begin{verbatim} - sudo mkdir -p $EXSCRIPT_LOG $EXSCRIPT_SPOOL $EXSCRIPT_CFG -\end{verbatim} - -\item Copy the config file: - -\begin{verbatim} - sudo cp config.xml.tmpl $EXSCRIPT_CFG/config.xml -\end{verbatim} - -\item Edit the config file: You need to define the user accounts -that are used by Exscript to contact your hosts. -In other words, edit the section accordingly. -To encrypt a password, the following command may be used: - -\begin{verbatim} - python -c 'import base64; print base64.b64encode("thepassword")' -\end{verbatim} - -\item Copy the init script: - -\begin{verbatim} - sudo cp util/exscriptd_init.sh /etc/init.d/exscriptd -\end{verbatim} - -\item Set the permissions: - -\begin{verbatim} - sudo chown -R $EXSCRIPT_USER:$EXSCRIPT_GROUP \ - $EXSCRIPT_LOG $EXSCRIPT_SPOOL $EXSCRIPT_CFG -\end{verbatim} - -\item If you used a custom installation path, you'll need to edit -the path in /etc/init.d/exscriptd accordingly. - -\item You may now test the daemon. This command starts the server: - -\begin{verbatim} - sudo /etc/init.d/exscriptd start -\end{verbatim} - -\hint{By default, the Exscript daemon does not provide -any services. In other words, all orders sent to the daemon -are rejected.} - -\item To define a service, you'll need to create an XML file -similar to the one shipped in - -\begin{verbatim} - tests/Exscript/daemon/testservice/test.xml -\end{verbatim} - -Don't forget to make the service file readable by exscriptd. -You may then tell exscriptd to load the service by -adding it to /etc/exscript/config.xml using the -XML tag under the daemon. For example: - -\begin{verbatim} - -\end{verbatim} - -\item To automatically start the daemon at boot time, use the following -command: - -\begin{verbatim} - sudo update-rc.d exscriptd defaults -\end{verbatim} -\end{enumerate} - - \begin{appendix} \section{The Standard Library} \label{stdlib} diff --git a/doc/mkapidoc.py b/doc/mkapidoc.py index 45b00e20..6e18da55 100644 --- a/doc/mkapidoc.py +++ b/doc/mkapidoc.py @@ -26,20 +26,6 @@ r'--exclude ^Exscript\.workqueue$', r'--exclude ^Exscript\.version$', r'--exclude-introspect ^Exscript\.util\.sigintcatcher$', - r'--exclude ^Exscriptd\.Config$', - r'--exclude ^Exscriptd\.ConfigReader$', - r'--exclude ^Exscriptd\.Daemon$', - r'--exclude ^Exscriptd\.DBObject$', - r'--exclude ^Exscriptd\.HTTPDaemon$', - r'--exclude ^Exscriptd\.HTTPDigestServer$', - r'--exclude ^Exscriptd\.OrderDB$', - r'--exclude ^Exscriptd\.PythonService$', - r'--exclude ^Exscriptd\.Service$', - r'--exclude ^Exscriptd\.Task$', - r'--exclude ^Exscriptd\.config$', - r'--exclude ^Exscriptd\.daemonize$', - r'--exclude ^Exscriptd\.util$', - r'--exclude ^Exscriptd\.pidutil$', '--html', '--no-private', '--introspect-only', @@ -48,7 +34,6 @@ '--inheritance=included', '-v', '-o %s' % doc_dir, - os.path.join(base_dir, project), - os.path.join(base_dir, 'Exscriptd')]) + os.path.join(base_dir, project)]) print cmd os.system(cmd) diff --git a/exclaim b/exclaim deleted file mode 100755 index 73e08bda..00000000 --- a/exclaim +++ /dev/null @@ -1,281 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import os -import sys -import time -import codecs -from optparse import OptionParser -from Exscript import __version__ -from Exscriptd import Client, Order - -parser = OptionParser(usage = '%prog [options] ip[:port] [filename]', - version = __version__) -parser.add_option('--user', '-u', - dest = 'user', - metavar = 'STRING', - help = ''' -The username on the server. -'''.strip()) -parser.add_option('--password', '-p', - dest = 'password', - metavar = 'STRING', - help = ''' -The password on the server. -'''.strip()) -parser.add_option('--wait', '-w', - dest = 'wait', - action = 'store_true', - default = False, - help = ''' -After placing the order, wait until it is completed. -'''.strip()) -parser.add_option('--csv', - dest = 'csv', - action = 'store_true', - default = False, - help = ''' -The order is using CSV format. -'''.strip()) -parser.add_option('--encoding', - dest = 'encoding', - metavar = 'STRING', - default = 'utf-8', - help = ''' -The encoding of the input file when using --csv. Defaults to utf8. -'''.strip()) -parser.add_option('--service', - dest = 'service', - metavar = 'STRING', - help = ''' -Send to the service with the given name, instead of -the service that is specified in the order. -'''.strip()) -parser.add_option('--status', '-s', - dest = 'status', - metavar = 'STRING', - help = ''' -Prints the status of the order with the given id and exit. -'''.strip()) -parser.add_option('--test', - dest = 'test', - action = 'store_true', - default = False, - help = ''' -Print the XML formatted order to stdout. -'''.strip()) -parser.add_option('--order-get', - dest = 'order_get', - metavar = 'INTEGER', - help = ''' -Prints the order with the given id. -'''.strip()) -parser.add_option('--order-count', - dest = 'order_count', - action = 'store_true', - default = False, - help = ''' -Prints the total number of orders. -'''.strip()) -parser.add_option('--order-list', - dest = 'order_list', - action = 'store_true', - default = False, - help = ''' -Lists the last 50 orders. -'''.strip()) -parser.add_option('--task-get', - dest = 'task_get', - metavar = 'INTEGER', - help = ''' -Prints the task with the given id. -'''.strip()) -parser.add_option('--task-count', - dest = 'task_count', - metavar = 'INTEGER', - help = ''' -Prints the number of tasks in the order with the given id. -'''.strip()) -parser.add_option('--task-list', - dest = 'task_list', - metavar = 'INTEGER', - help = ''' -Lists the last 50 tasks of the order with the given id. -'''.strip()) -parser.add_option('--quite', '-q', - dest = 'quite', - action = 'store_true', - default = False, - help = ''' -Do not write anything to stdout. -'''.strip()) - -# Parse options. -options, args = parser.parse_args(sys.argv) -args.pop(0) - -if not options.test: - if not options.user: - parser.error('Required option --user not set') - if not options.password: - parser.error('Required option --password not set') - -try: - address = args.pop(0) -except IndexError: - parser.error('ip argument missing') - -if options.status: - pass -elif options.order_get: - pass -elif options.order_count: - pass -elif options.order_list: - pass -elif options.task_get: - pass -elif options.task_count: - pass -elif options.task_list: - pass -else: - try: - filename = args.pop(0) - except IndexError: - parser.error('order argument missing') - -if args: - parser.error('too many arguments') - -def say(*args): - if not options.quite: - sys.stdout.write(' '.join(str(a) for a in args) + '\n') - -def print_table(rows): - if not rows: - return - widths = [1 for h in rows[0]] - for row in rows: - for n, col in enumerate(row): - widths[n] = max(widths[n], len(str(col)) + 2) - for i, row in enumerate(rows): - for n, col in enumerate(row): - sys.stdout.write(str(col).ljust(widths[n])) - print - if i == 0: - print '-' * sum(widths) - -client = Client(address, options.user, options.password) - -if options.status: - status, progress, closed = client.get_order_status_from_id(options.status) - print status, '%.1f%%' % (progress * 100.0), closed - sys.exit(0) -elif options.order_get: - order = client.get_order_from_id(int(options.order_get)) - print 'ID:', order.get_id() - print 'Service:', order.get_service_name() - print 'Status:', order.get_status() - print 'Description:', order.get_description() - print 'Created by:', order.get_created_by() - print 'Created:', order.get_created_timestamp() - print 'Closed:', order.get_closed_timestamp() - sys.exit(0) -elif options.order_count: - print client.count_orders() - sys.exit(0) -elif options.order_list: - rows = [('OrderID', - 'Service', - 'Description', - 'Status', - 'Progress', - 'Created by', - 'Created', - 'Closed')] - for order in client.get_order_list(offset = 0, limit = 100): - row = (order.get_id(), - order.get_service_name(), - order.get_description(), - order.get_status(), - order.get_progress_percent(), - order.get_created_by(), - str(order.get_created_timestamp()), - str(order.get_closed_timestamp() or 'open')) - rows.append(row) - print_table(rows) - sys.exit(0) -elif options.task_get: - task = client.get_task_from_id(int(options.task_get)) - print 'ID:', task.get_id() - print 'Name:', task.get_name() - print 'Status:', task.get_status() - print 'Started:', task.get_started_timestamp() - print 'Closed:', task.get_closed_timestamp() - print 'Logfile:', task.get_logfile() - print 'Trace file:', task.get_tracefile() - sys.exit(0) -elif options.task_count: - print client.count_tasks(int(options.task_count)) - sys.exit(0) -elif options.task_list: - rows = [('TaskID', - 'Name', - 'Status', - 'Progress', - 'Started', - 'Closed')] - for task in client.get_task_list(int(options.task_list), 0, 100): - row = (task.get_id(), - task.get_name(), - task.get_status(), - '%.1f' % (task.get_progress() * 100.0), - str(task.get_started_timestamp()), - str(task.get_closed_timestamp() or 'open')) - rows.append(row) - print_table(rows) - sys.exit(0) - -try: - codecs.lookup(options.encoding) -except LookupError: - parser.error('no such encoding: ' + str(options.encoding)) - -if options.csv: - if not options.service: - parser.error('Need --service when using --csv.') - order = Order.from_csv_file(options.service, filename, options.encoding) -else: - order = Order.from_xml_file(filename) - if options.service: - order.set_service_name(options.service) - -if options.test: - print order.toxml() - sys.exit(0) - -client.place_order(order) -say('Placed order', order.id) - -if options.wait: - while True: - status, progress, closed = client.get_order_status_from_id(order.id) - say('Status:', status) - if closed is not None: - print 'Order', order.id, 'closed', - print 'in status', status, 'on', str(closed) + '.' - break - time.sleep(1) diff --git a/exscriptd b/exscriptd deleted file mode 100755 index b03cedb5..00000000 --- a/exscriptd +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import sys -import os -import logging -from optparse import OptionParser -from Exscript import __version__ -from Exscript.util import pidutil, daemonize -from Exscriptd.Config import Config, default_config_dir -from Exscriptd.Dispatcher import Dispatcher - -parser = OptionParser(usage = '%prog [options] [start|stop|restart]', - version = __version__) -parser.add_option('--config-dir', - dest = 'config_dir', - default = default_config_dir, - metavar = 'FILE', - help = ''' -The XML config file for the Exscript daemon. -'''.strip()) -parser.add_option('--pidfile', - dest = 'pidfile', - metavar = 'FILE', - help = ''' -The file used for tracking the process id of the daemon. -'''.strip()) -parser.add_option('--verbose', - dest = 'verbose', - action = 'store_true', - default = False, - help = 'Show the conversation with the router') - -# Parse options. -options, args = parser.parse_args(sys.argv) -args.pop(0) - -try: - action = args.pop(0) -except IndexError: - parser.error('action argument not specified') - -if not options.config_dir: - parser.error('required option --config-dir not set') - -# Make sure that the daemon is not already running. -if not options.pidfile: - options.pidfile = os.path.join(options.config_dir, 'exscript.pid') - -if action == 'stop': - if not pidutil.isalive(options.pidfile): - parser.error('no running daemon found') - pidutil.kill(options.pidfile) - print "exscriptd stopped." - sys.exit(0) -if action == 'start' and pidutil.isalive(options.pidfile): - parser.error('daemon is already running') -if action == 'restart': - pidutil.kill(options.pidfile) - print "exscriptd stopped." - -daemonize.daemonize() - -logger = logging.getLogger('exscript') -logger.setLevel(logging.INFO) - -# Catch output that is written to stdout, and redirect it to -# Python's logging. -class LoggingIO(object): - def __init__(self, logger, channel = logging.INFO): - self.logger = logger - self.channel = channel - - def write(self, data): - data = data.rstrip() - if data: - self.logger.log(self.channel, data) - - def flush(self): - pass - -sys.stdout = LoggingIO(logger) -sys.stderr = LoggingIO(logger, logging.ERROR) - -# Read the config. -config = Config(options.config_dir) -logger.info('config read') -if not os.path.isdir(config.get_logdir()): - os.makedirs(config.get_logdir()) -logfile = os.path.join(config.get_logdir(), 'exscriptd.log') - -# Set up logfile rotation. -handler = logging.handlers.RotatingFileHandler(logfile, - maxBytes = 2000000, - backupCount = 10) -formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") -handler.setFormatter(formatter) -logger.addHandler(handler) -logging.info('exscriptd daemonized') - -# Init. -order_db = config.get_order_db() -logger.info('order db initialized') -queues = config.get_queues() -logger.info('queues initialized') -dispatcher = Dispatcher(order_db, queues, logger, config.get_logdir()) -logger.info('dispatcher initialized') - -daemon = config.get_daemon(dispatcher) -logger.info('daemon initialized') -services = config.get_services(dispatcher) -logger.info('services initialized') - -if not services: - dir = os.path.join(options.config_dir, 'services') - msg = 'error: %s contains no enabled services\n' % dir - sys.stderr.write(msg) - sys.exit(1) - -logger.info('config is ok, starting daemon') - -pidutil.write(options.pidfile) -logger.info('starting daemon ' + repr(daemon.name)) -daemon.run() -pidutil.remove(options.pidfile) diff --git a/exscriptd-config b/exscriptd-config deleted file mode 100755 index 0e394c11..00000000 --- a/exscriptd-config +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import os -import sys -from optparse import OptionParser -from Exscript import __version__ -from Exscriptd.Config import default_config_dir -from Exscriptd.config import modules - -__dirname__ = os.path.dirname(__file__) - -def get_usage(section = 'section', command = 'command'): - return '%%prog [options] %s [options] %s [...]\n' % (section, command) - -# Parse global options (all options before the first positional argument). -usage = get_usage() + 'Sections:' -for module_name, module in modules.iteritems(): - usage += '\n ' + module_name.ljust(8) + '\t' + module.get_description() - -parser = OptionParser(usage = usage, version = __version__) -parser.disable_interspersed_args() -parser.add_option('--config-dir', - dest = 'config_dir', - default = default_config_dir, - metavar = 'FILE', - help = ''' -The XML config file for the Exscript daemon. -'''.strip()) - -global_options, args = parser.parse_args() - -# Parse section-specific options (all options before the second positional -# argument). -try: - section = args.pop(0) -except IndexError: - parser.error('section argument missing') - -try: - module = modules[section] -except KeyError: - parser.error('no such section: %s' % section) - -def which(program): - def is_exe(fpath): - return os.path.exists(fpath) and os.access(fpath, os.X_OK) - - fpath, fname = os.path.split(program) - if fpath: - if is_exe(program): - return program - else: - for path in os.environ["PATH"].split(os.pathsep): - exe_file = os.path.join(path, program) - if is_exe(exe_file): - return exe_file - return None - -exscript_binary = which('exscriptd') -if exscript_binary is None: - path = repr(os.environ.get('PATH')) - parser.error('exscriptd executable not found. PATH was ' + path) -print 'exscriptd found at ' + exscript_binary -script_dir = os.path.dirname(exscript_binary) - -handler = module(global_options, script_dir = script_dir) -usage = get_usage(section) + 'Commands:' -for command_name, command in handler.get_commands(): - usage += '\n ' + command_name.ljust(8) + '\t' + command - -parser = OptionParser(usage = usage) -parser.disable_interspersed_args() -section_options, args = parser.parse_args(args) - -# Parse command-specific options (all remaining options). -try: - command = args.pop(0) -except IndexError: - parser.error('command argument missing') - -# Get the command handler. -try: - start = getattr(handler, 'start_' + command) -except AttributeError: - parser.error('no such command: %s' % command) - -usage = get_usage(section, command) -parser = OptionParser(usage = usage) -try: - optadd = getattr(handler, 'getopt_' + command) -except AttributeError: - pass -else: - optadd(parser) -handler.options, args = parser.parse_args(args) - -# Check the command specific arguments. -try: - prepare = getattr(handler, 'prepare_' + command) -except AttributeError: - pass -else: - try: - prepare(parser, *args) - except TypeError: - parser.error('invalid number of arguments for this command') - -start() diff --git a/setup.py b/setup.py index eeda4c0e..8481e183 100644 --- a/setup.py +++ b/setup.py @@ -22,24 +22,13 @@ author_email = 'knipknap@gmail.com', license = 'GPLv2', package_dir = {'': 'src', - 'Exscript': os.path.join('src', 'Exscript'), - 'Exscriptd': os.path.join('src', 'Exscriptd')}, - package_data = {'Exscriptd': [ - os.path.join('config', 'exscriptd.in'), - os.path.join('config', 'main.xml.in'), - ]}, + 'Exscript': os.path.join('src', 'Exscript')}, + package_data = {}, packages = find_packages('src'), - scripts = ['exscript', - 'exscriptd', - 'exscriptd-config', - 'exclaim'], + scripts = ['exscript'], install_requires = ['paramiko', 'pycrypto'], - extras_require = { - 'Exscriptd': ['sqlalchemy', 'lxml'], - }, + extras_require = {}, keywords = ' '.join(['exscript', - 'exscripd', - 'exclaim', 'telnet', 'ssh', 'network', diff --git a/src/Exscriptd/Client.py b/src/Exscriptd/Client.py deleted file mode 100644 index 69a99060..00000000 --- a/src/Exscriptd/Client.py +++ /dev/null @@ -1,291 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" -Places orders and requests the status from a server. -""" -import json -#HACK: the return value of opener.open(url) does not support -# __enter__ and __exit__ for Python's "with" in older versions -# of Python. -import urllib -urllib.addinfourl.__enter__ = lambda x: x -urllib.addinfourl.__exit__ = lambda s, *x: s.close() -#END HACK -from datetime import datetime -from urllib import urlencode -from urllib2 import HTTPDigestAuthHandler, build_opener, HTTPError -from lxml import etree -from Exscript.servers.HTTPd import default_realm -from Exscriptd.Order import Order -from Exscriptd.Task import Task - -class Client(object): - """ - An Exscriptd client that communicates via HTTP. - """ - - def __init__(self, address, user, password): - """ - Constructor. Any operations performed with an - instance of a client are directed to the server with the - given address, using the given login data. - - @type address: str - @param address: The base url of the server. - @type user: str - @param user: The login name on the server. - @type password: str - @param password: The password of the user. - """ - self.address = 'http://' + address - self.handler = HTTPDigestAuthHandler() - self.opener = build_opener(self.handler) - self.handler.add_password(realm = default_realm, - uri = self.address, - user = user, - passwd = password) - - def place_order(self, order): - """ - Sends the given order to the server, and updates the status - of the order accordingly. - - @type order: Order - @param order: The order that is placed. - """ - if order.status != 'new': - msg = 'order status is "%s", should be "new"' % order.status - raise ValueError(msg) - if not order.is_valid(): - raise ValueError('incomplete or invalid order') - - order.status = 'accepted' - url = self.address + '/order/' - xml = order.toxml() - data = urlencode({'xml': xml}) - try: - with self.opener.open(url, data) as result: - if result.getcode() != 200: - raise Exception(result.read()) - order.id = json.loads(result.read()) - except HTTPError, e: - if hasattr(e, 'read'): - raise Exception(str(e) + ' with ' + e.read()) - else: - raise Exception(str(e)) - - def get_order_from_id(self, id): - """ - Returns the order with the given id. - - @type id: str - @param id: The id of the order. - @rtype: Order - @return: The order if it exists, None otherwise. - """ - args = 'id=%d' % id - url = self.address + '/order/get/?' + args - with self.opener.open(url) as result: - if result.getcode() != 200: - raise Exception(response) - return Order.from_xml(result.read()) - - def get_order_status_from_id(self, order_id): - """ - Returns a tuple containing the status of the order with the given - id if it exists. Raises an exception otherwise. The tuple contains - the following elements:: - - status, progress, closed - - where 'status' is a human readable string, progress is a - floating point number between 0.0 and 1.0, and closed is the - time at which the order was closed. - - @type order_id: str - @param order_id: The id of the order. - @rtype: (str, float, datetime.timestamp) - @return: The status and progress of the order. - """ - url = self.address + '/order/status/?id=%s' % order_id - with self.opener.open(url) as result: - response = result.read() - if result.getcode() != 200: - raise Exception(response) - data = json.loads(response) - closed = data['closed'] - if closed is not None: - closed = closed.split('.', 1)[0] - closed = datetime.strptime(closed, "%Y-%m-%d %H:%M:%S") - return data['status'], data['progress'], closed - - def count_orders(self, - order_id = None, - service = None, - description = None, - status = None, - created_by = None): - """ - Returns the total number of orders. - - @rtype: int - @return: The number of orders. - @type kwargs: dict - @param kwargs: See L{get_order_list()} - """ - args = {} - if order_id: - args['order_id'] = order_id - if service: - args['service'] = service - if description: - args['description'] = description - if status: - args['status'] = status - if created_by: - args['created_by'] = created_by - url = self.address + '/order/count/?' + urlencode(args) - with self.opener.open(url) as result: - response = result.read() - if result.getcode() != 200: - raise Exception(response) - return json.loads(response) - - def get_order_list(self, - order_id = None, - service = None, - description = None, - status = None, - created_by = None, - offset = 0, - limit = 0): - """ - Returns a list of currently running orders. - - @type offset: int - @param offset: The number of orders to skip. - @type limit: int - @param limit: The maximum number of orders to return. - @type kwargs: dict - @param kwargs: The following keys may be used: - - order_id - the order id (str) - - service - the service name (str) - - description - the order description (str) - - status - the status (str) - - created_by - the user name (str) - @rtype: list[Order] - @return: A list of orders. - """ - args = {'offset': offset, 'limit': limit} - if order_id: - args['order_id'] = order_id - if service: - args['service'] = service - if description: - args['description'] = description - if status: - args['status'] = status - if created_by: - args['created_by'] = created_by - url = self.address + '/order/list/?' + urlencode(args) - with self.opener.open(url) as result: - if result.getcode() != 200: - raise Exception(response) - xml = etree.parse(result) - return [Order.from_etree(n) for n in xml.iterfind('order')] - - def count_tasks(self, order_id = None): - """ - Returns the total number of tasks. - - @rtype: int - @return: The number of tasks. - """ - args = '' - if order_id: - args += '?order_id=%d' % order_id - url = self.address + '/task/count/' + args - with self.opener.open(url) as result: - response = result.read() - if result.getcode() != 200: - raise Exception(response) - return json.loads(response) - - def get_task_list(self, order_id, offset = 0, limit = 0): - """ - Returns a list of currently running orders. - - @type offset: int - @param offset: The number of orders to skip. - @type limit: int - @param limit: The maximum number of orders to return. - @rtype: list[Order] - @return: A list of orders. - """ - args = 'order_id=%d&offset=%d&limit=%d' % (order_id, offset, limit) - url = self.address + '/task/list/?' + args - with self.opener.open(url) as result: - if result.getcode() != 200: - raise Exception(response) - xml = etree.parse(result) - return [Task.from_etree(n) for n in xml.iterfind('task')] - - def get_task_from_id(self, id): - """ - Returns the task with the given id. - - @type id: int - @param id: The id of the task. - @rtype: Task - @return: The task with the given id. - """ - args = 'id=%d' % id - url = self.address + '/task/get/?' + args - with self.opener.open(url) as result: - if result.getcode() != 200: - raise Exception(response) - return Task.from_xml(result.read()) - - def get_log_from_task_id(self, task_id): - """ - Returns the content of the logfile for the given task. - - @type task_id: int - @param task_id: The task id. - @rtype: str - @return: The file content. - """ - args = 'task_id=%d' % task_id - url = self.address + '/log/?' + args - with self.opener.open(url) as result: - if result.getcode() != 200: - raise Exception(response) - return result.read() - - def get_trace_from_task_id(self, task_id): - """ - Returns the content of the trace file for the given task. - - @type task_id: int - @param task_id: The task id. - @rtype: str - @return: The file content. - """ - args = 'task_id=%d' % task_id - url = self.address + '/trace/?' + args - with self.opener.open(url) as result: - if result.getcode() != 200: - raise Exception(response) - return result.read() diff --git a/src/Exscriptd/Config.py b/src/Exscriptd/Config.py deleted file mode 100644 index 0f9c9872..00000000 --- a/src/Exscriptd/Config.py +++ /dev/null @@ -1,452 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import os -import base64 -import shutil -import logging -import logging.handlers -from functools import partial -from lxml import etree -from Exscript import Queue -from Exscript.AccountPool import AccountPool -from Exscript.util.file import get_accounts_from_file -from Exscriptd.OrderDB import OrderDB -from Exscriptd.HTTPDaemon import HTTPDaemon -from Exscriptd.Service import Service -from Exscriptd.ConfigReader import ConfigReader -from Exscriptd.util import find_module_recursive -from Exscriptd.xml import get_accounts_from_etree, add_accounts_to_etree - -default_config_dir = os.path.join('/etc', 'exscriptd') -default_log_dir = os.path.join('/var', 'log', 'exscriptd') - -cache = {} -def cache_result(func): - def wrapped(*args, **kwargs): - key = func.__name__, repr(args), repr(kwargs) - if key not in cache: - cache[key] = func(*args, **kwargs) - return cache[key] - return wrapped - -class Config(ConfigReader): - def __init__(self, - cfg_dir = default_config_dir, - resolve_variables = True): - self.cfg_dir = cfg_dir - self.service_dir = os.path.join(cfg_dir, 'services') - filename = os.path.join(cfg_dir, 'main.xml') - self.logdir = default_log_dir - ConfigReader.__init__(self, filename, resolve_variables) - - logdir_elem = self.cfgtree.find('exscriptd/logdir') - if logdir_elem is not None: - self.logdir = logdir_elem.text - - def _get_account_list_from_name(self, name): - element = self.cfgtree.find('account-pool[@name="%s"]' % name) - return get_accounts_from_etree(element) - - @cache_result - def _init_account_pool_from_name(self, name): - print 'Creating account pool "%s"...' % name - accounts = self._get_account_list_from_name(name) - return AccountPool(accounts) - - @cache_result - def _init_queue_from_name(self, name): - # Create the queue first. - element = self.cfgtree.find('queue[@name="%s"]' % name) - max_threads = element.find('max-threads').text - queue = Queue(verbose = 0, max_threads = max_threads) - - # Assign account pools to the queue. - def match_cb(condition, host): - return eval(condition, host.get_dict()) - - for pool_elem in element.iterfind('account-pool'): - pname = pool_elem.text - pool = self._init_account_pool_from_name(pname) - cond = pool_elem.get('for') - if cond is None: - print 'Assigning default account pool "%s" to "%s"...' % (pname, name) - queue.add_account_pool(pool) - continue - - print 'Assigning account pool "%s" to "%s"...' % (pname, name) - condition = compile(cond, 'config', 'eval') - queue.add_account_pool(pool, partial(match_cb, condition)) - - return queue - - def get_logdir(self): - return self.logdir - - def set_logdir(self, logdir): - self.logdir = logdir - # Create an XML segment for the database. - changed = False - xml = self.cfgtree.getroot() - global_elem = xml.find('exscriptd') - if global_elem is None: - raise Exception('missing section in config file: ') - - # Add the dbn the the XML. - if self._add_or_update_elem(global_elem, 'logdir', logdir): - changed = True - - # Write the resulting XML. - if not changed: - return False - self.save() - return True - - def get_queues(self): - names = [e.get('name') for e in self.cfgtree.iterfind('queue')] - return dict((name, self._init_queue_from_name(name)) - for name in names) - - def _init_database_from_dbn(self, dbn): - from sqlalchemy import create_engine - from sqlalchemy.pool import NullPool - #print 'Creating database connection for', dbn - return create_engine(dbn, poolclass = NullPool) - - def _init_daemon(self, element, dispatcher): - # Init the order database for the daemon. - name = element.get('name') - address = element.find('address').text or '' - port = int(element.find('port').text) - - # Create log directories for the daemon. - logdir = os.path.join(self.logdir, 'daemons', name) - logfile = os.path.join(logdir, 'access.log') - if not os.path.isdir(logdir): - os.makedirs(logdir) - - # Set up logging. - logger = logging.getLogger('exscriptd_' + name) - logger.setLevel(logging.INFO) - - # Set up logfile rotation. - handler = logging.handlers.RotatingFileHandler(logfile, - maxBytes = 200000, - backupCount = 5) - - # Define the log format. - formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") - handler.setFormatter(formatter) - logger.addHandler(handler) - - # Create the daemon (this does not start it). - daemon = HTTPDaemon(dispatcher, name, logger, address, port) - - # Add some accounts, if any. - account_pool = element.find('account-pool') - for account in self._get_account_list_from_name(account_pool.text): - daemon.add_account(account) - - return daemon - - def _get_service_files(self): - """ - Searches the config directory for service configuration files, - and returns a list of path names. - """ - files = [] - for file in os.listdir(self.service_dir): - config_dir = os.path.join(self.service_dir, file) - if not os.path.isdir(config_dir): - continue - config_file = os.path.join(config_dir, 'config.xml') - if not os.path.isfile(config_file): - continue - files.append(config_file) - return files - - def _get_service_file_from_name(self, name): - """ - Searches the config directory for service configuration files, - and returns the name of the first file that defines a service - with the given name. - """ - for file in self._get_service_files(): - xml = etree.parse(file) - element = xml.find('service[@name="%s"]' % name) - if element is not None: - return file - return None - - def _get_service_file_from_folder(self, folder): - """ - Searches the config directory for service configuration files, - and returns the name of the first file lying in the folder - with the given name. - """ - for file in self._get_service_files(): - if os.path.basename(os.path.dirname(file)) == folder: - return file - return None - - def _init_service_file(self, filename, dispatcher): - services = [] - service_dir = os.path.dirname(filename) - cfgtree = ConfigReader(filename).cfgtree - for element in cfgtree.iterfind('service'): - name = element.get('name') - print 'Loading service "%s"...' % name - - module = element.find('module').text - queue_elem = element.find('queue') - queue_name = queue_elem is not None and queue_elem.text - service = Service(dispatcher, - name, - module, - service_dir, - self, - queue_name) - print 'Service "%s" initialized.' % name - services.append(service) - return services - - def get_services(self, dispatcher): - services = [] - for file in self._get_service_files(): - services += self._init_service_file(file, dispatcher) - return services - - def has_account_pool(self, name): - return self.cfgtree.find('account-pool[@name="%s"]' % name) is not None - - def add_account_pool_from_file(self, name, filename): - # Remove the pool if it exists. - xml = self.cfgtree.getroot() - pool_elem = xml.find('account-pool[@name="%s"]' % name) - if pool_elem is not None: - xml.remove(pool_elem) - - # Import the new pool from the given file. - pool_elem = etree.SubElement(xml, 'account-pool', name = name) - accounts = get_accounts_from_file(filename) - add_accounts_to_etree(pool_elem, accounts) - - self.save() - - def has_queue(self, name): - return self.cfgtree.find('queue[@name="%s"]' % name) is not None - - def has_database(self, name): - return self.cfgtree.find('database[@name="%s"]' % name) is not None - - def add_database(self, db_name, dbn): - # Create an XML segment for the database. - changed = False - xml = self.cfgtree.getroot() - db_elem = xml.find('database[@name="%s"]' % db_name) - if db_elem is None: - changed = True - db_elem = etree.SubElement(xml, 'database', name = db_name) - - # Add the dbn the the XML. - if self._add_or_update_elem(db_elem, 'dbn', dbn): - changed = True - - # Write the resulting XML. - if not changed: - return False - self.save() - return True - - @cache_result - def get_database_from_name(self, name): - element = self.cfgtree.find('database[@name="%s"]' % name) - dbn = element.find('dbn').text - return self._init_database_from_dbn(dbn) - - @cache_result - def get_order_db(self): - db_elem = self.cfgtree.find('exscriptd/order-db') - if db_elem is None: - engine = self._init_database_from_dbn('sqlite://') - else: - engine = self.get_database_from_name(db_elem.text) - db = OrderDB(engine) - db.install() - return db - - def add_queue(self, queue_name, account_pool, max_threads): - # Create an XML segment for the queue. - changed = False - xml = self.cfgtree.getroot() - queue_elem = xml.find('queue[@name="%s"]' % queue_name) - if queue_elem is None: - changed = True - queue_elem = etree.SubElement(xml, 'queue', name = queue_name) - - # Create an XML reference to the account pool. - acc_elem = queue_elem.find('account-pool') - if account_pool is None and acc_elem is not None: - changed = True - queue_elem.remove(acc_elem) - elif account_pool is not None: - try: - self._init_account_pool_from_name(account_pool) - except AttributeError: - raise Exception('no such account pool: %s' % account_pool) - - if self._add_or_update_elem(queue_elem, - 'account-pool', - account_pool): - changed = True - - # Define the number of threads. - if self._add_or_update_elem(queue_elem, 'max-threads', max_threads): - changed = True - - if not changed: - return False - - # Write the resulting XML. - self.save() - return True - - def has_service(self, service_name): - return self._get_service_file_from_name(service_name) is not None - - def add_service(self, - service_name, - module_name = None, - daemon_name = None, - queue_name = None): - pathname = self._get_service_file_from_name(service_name) - changed = False - - if not pathname: - if not module_name: - raise Exception('module name is required') - - # Find the installation path of the module. - file, module_path, desc = find_module_recursive(module_name) - - # Create a directory for the new service, if it does not - # already exist. - service_dir = os.path.join(self.service_dir, service_name) - if not os.path.isdir(service_dir): - os.makedirs(service_dir) - - # Copy the default config file. - cfg_file = os.path.join(module_path, 'config.xml.tmpl') - pathname = os.path.join(service_dir, 'config.xml') - if not os.path.isfile(pathname): - shutil.copy(cfg_file, pathname) - changed = True - - # Create an XML segment for the service. - doc = etree.parse(pathname) - xml = doc.getroot() - service_ele = xml.find('service[@name="%s"]' % service_name) - if service_ele is None: - changed = True - service_ele = etree.SubElement(xml, 'service', name = service_name) - - # By default, use the first daemon defined in the main config file. - if daemon_name is None: - daemon_name = self.cfgtree.find('daemon').get('name') - if self._add_or_update_elem(service_ele, 'daemon', daemon_name): - changed = True - - # Add an XML statement pointing to the module. - if module_name is not None: - if self._add_or_update_elem(service_ele, 'module', module_name): - changed = True - - # By default, use the first queue defined in the main config file. - if queue_name is None: - queue_name = self.cfgtree.find('queue').get('name') - if not self.has_queue(queue_name): - raise Exception('no such queue: ' + queue_name) - if self._add_or_update_elem(service_ele, 'queue', queue_name): - changed = True - - if not changed: - return False - - # Write the resulting XML. - self._write_xml(xml, pathname) - return True - - def _get_service_var_elem(self, service): - pathname = self._get_service_file_from_folder(service) - if pathname is None: - pathname = self._get_service_file_from_name(service) - doc = etree.parse(pathname) - xml = doc.getroot() - return pathname, xml, xml.find('variables') - - def set_service_variable(self, service, varname, value): - path, xml, var_elem = self._get_service_var_elem(service) - elem = var_elem.find(varname) - if elem is None: - elem = etree.SubElement(var_elem, varname) - elem.text = value - self._write_xml(xml, path) - - def unset_service_variable(self, service, varname): - path, xml, var_elem = self._get_service_var_elem(service) - elem = var_elem.find(varname) - if elem is not None: - var_elem.remove(elem) - self._write_xml(xml, path) - - def has_daemon(self, name): - return self.cfgtree.find('daemon[@name="%s"]' % name) is not None - - def add_daemon(self, - name, - address, - port, - account_pool, - database): - daemon_elem = self.cfgtree.find('daemon[@name="%s"]' % name) - changed = False - if daemon_elem is None: - changed = True - daemon_elem = etree.SubElement(self.cfgtree.getroot(), - 'daemon', - name = name) - - if self._add_or_update_elem(daemon_elem, 'address', address): - changed = True - if self._add_or_update_elem(daemon_elem, 'port', port): - changed = True - if self._add_or_update_elem(daemon_elem, 'account-pool', account_pool): - changed = True - if self._add_or_update_elem(daemon_elem, 'database', database): - changed = True - - if not changed: - return False - self.save() - return changed - - @cache_result - def get_daemon_from_name(self, name, dispatcher): - # Create the daemon. - element = self.cfgtree.find('daemon[@name="%s"]' % name) - return self._init_daemon(element, dispatcher) - - def get_daemon(self, dispatcher): - name = self.cfgtree.find('exscriptd/daemon').text - return self.get_daemon_from_name(name, dispatcher) diff --git a/src/Exscriptd/ConfigReader.py b/src/Exscriptd/ConfigReader.py deleted file mode 100644 index 8d97616b..00000000 --- a/src/Exscriptd/ConfigReader.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import os -import inspect -import shutil -from lxml import etree -from Exscriptd.util import resolve_variables - -class ConfigReader(object): - def __init__(self, filename, resolve_variables = True, parent = None): - clsfile = inspect.getfile(self.__class__) - self.resolve = resolve_variables - self.cfgtree = etree.parse(filename) - self.filename = filename - self.parent = parent - self.variables = os.environ.copy() - self.variables['INSTALL_DIR'] = os.path.dirname(clsfile) - self._clean_tree() - - def _resolve(self, text): - if not self.resolve: - return text - if text is None: - return None - return resolve_variables(self.variables, text.strip()) - - def _clean_tree(self): - # Read all variables. - variables = self.cfgtree.find('variables') - if variables is not None: - for element in variables: - varname = element.tag.strip() - value = resolve_variables(self.variables, element.text) - self.variables[varname] = value - - # Resolve variables everywhere. - for element in self.cfgtree.iter(): - if element.tag is etree.Comment: - continue - element.text = self._resolve(element.text) - for attr in element.attrib: - value = element.attrib[attr] - element.attrib[attr] = self._resolve(value) - - def _add_or_update_elem(self, parent, name, text): - child_elem = parent.find(name) - changed = False - if child_elem is None: - changed = True - child_elem = etree.SubElement(parent, name) - if str(child_elem.text) != str(text): - changed = True - child_elem.text = str(text) - return changed - - def _write_xml(self, tree, filename): - if os.path.isfile(filename): - shutil.move(filename, filename + '.old') - with open(filename, 'w') as fp: - fp.write(etree.tostring(tree, pretty_print = True)) - - def _findelem(self, selector): - elem = self.cfgtree.find(selector) - if elem is not None: - return elem - if self.parent is None: - return None - return self.parent._findelem(selector) - - def save(self): - self._write_xml(self.cfgtree, self.filename) diff --git a/src/Exscriptd/DBObject.py b/src/Exscriptd/DBObject.py deleted file mode 100644 index 574aab91..00000000 --- a/src/Exscriptd/DBObject.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -class DBObject(object): - def __init__(self, obj = None): - # Since we override setattr below, we can't access our properties - # directly. - self.__dict__['__object__'] = obj - self.__dict__['__changed__'] = True - - def __setattr__(self, name, value): - """ - Overwritten to proxy any calls to the associated object - (decorator pattern). - - @type name: string - @param name: The attribute name. - @type value: string - @param value: The attribute value. - """ - if self.__dict__.get('__object__') is None: - self.__dict__[name] = value - if name in self.__dict__.keys(): - self.__dict__[name] = value - else: - setattr(self.__object__, name, value) - - def __getattr__(self, name): - """ - Overwritten to proxy any calls to the associated object - (decorator pattern). - - @type name: string - @param name: The attribute name. - @rtype: object - @return: Whatever the protocol adapter returns. - """ - if self.__dict__.get('__object__') is None: - return self.__dict__[name] - if name in self.__dict__.keys(): - return self.__dict__[name] - return getattr(self.__object__, name) - - def touch(self): - self.__dict__['__changed__'] = True - - def untouch(self): - self.__dict__['__changed__'] = False - - def is_dirty(self): - return self.__dict__['__changed__'] diff --git a/src/Exscriptd/Dispatcher.py b/src/Exscriptd/Dispatcher.py deleted file mode 100644 index f869c3d2..00000000 --- a/src/Exscriptd/Dispatcher.py +++ /dev/null @@ -1,250 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import os -import logging -from functools import partial -from collections import defaultdict -from threading import Thread, Lock -from Exscriptd.util import synchronized -from Exscriptd import Task - -class _AsyncFunction(Thread): - def __init__ (self, function, *args, **kwargs): - Thread.__init__(self) - self.function = function - self.args = args - self.kwargs = kwargs - - def run(self): - self.function(*self.args, **self.kwargs) - -class Dispatcher(object): - def __init__(self, order_db, queues, logger, logdir): - self.order_db = order_db - self.queues = {} - self.logger = logger - self.loggers = defaultdict(list) # map order id to loggers - self.logdir = logdir - self.lock = Lock() - self.services = {} - self.daemons = {} - self.logger.info('Closing all open orders.') - self.order_db.close_open_orders() - - if not os.path.isdir(logdir): - os.makedirs(logdir) - for name, queue in queues.iteritems(): - self.add_queue(name, queue) - - def get_queue_from_name(self, name): - return self.queues[name] - - def add_queue(self, name, queue): - self.queues[name] = queue - wq = queue.workqueue - wq.job_init_event.connect(partial(self._on_job_event, name, 'init')) - wq.job_started_event.connect(partial(self._on_job_event, - name, - 'started')) - wq.job_error_event.connect(partial(self._on_job_event, - name, - 'error')) - wq.job_succeeded_event.connect(partial(self._on_job_event, - name, - 'succeeded')) - wq.job_aborted_event.connect(partial(self._on_job_event, - name, - 'aborted')) - - def _set_task_status(self, job_id, queue_name, status): - # Log the status change. - task = self.order_db.get_task(job_id = job_id) - msg = '%s/%s: %s' % (queue_name, job_id, status) - if task is None: - self.logger.info(msg + ' (untracked)') - return - self.logger.info(msg + ' (order id ' + str(task.order_id) + ')') - - # Update the task in the database. - if status == 'succeeded': - task.completed() - elif status == 'started': - task.set_status('running') - elif status == 'aborted': - task.close(status) - else: - task.set_status(status) - self.order_db.save_task(task) - - # Check whether the order can now be closed. - if task.get_closed_timestamp() is not None: - order = self.order_db.get_order(id = task.order_id) - self._update_order_status(order) - - def _on_job_event(self, queue_name, status, job, *args): - self._set_task_status(job.id, queue_name, status) - - def set_job_name(self, job_id, name): - task = self.order_db.get_task(job_id = job_id) - task.set_name(name) - self.order_db.save_task(task) - - def set_job_progress(self, job_id, progress): - task = self.order_db.get_task(job_id = job_id) - task.set_progress(progress) - self.order_db.save_task(task) - - def get_order_logdir(self, order): - orders_logdir = os.path.join(self.logdir, 'orders') - order_logdir = os.path.join(orders_logdir, str(order.get_id())) - if not os.path.isdir(order_logdir): - os.makedirs(order_logdir) - return order_logdir - - def get_logger(self, order, name, level = logging.INFO): - """ - Creates a logger that logs to a file in the order's log directory. - """ - order_logdir = self.get_order_logdir(order) - logfile = os.path.join(order_logdir, name) - logger = logging.getLogger(logfile) - handler = logging.FileHandler(logfile) - format = r'%(asctime)s - %(levelname)s - %(message)s' - formatter = logging.Formatter(format) - logger.setLevel(logging.INFO) - handler.setFormatter(formatter) - logger.addHandler(handler) - self.loggers[order.get_id()].append(logger) - return logger - - def _free_logger(self, logger): - # hack to work around the fact that Python's logging module - # provides no documented way to delete loggers. - del logger.manager.loggerDict[logger.name] - logger.manager = None - - def service_added(self, service): - """ - Called by a service when it is initialized. - """ - service.parent = self - self.services[service.name] = service - - def daemon_added(self, daemon): - """ - Called by a daemon when it is initialized. - """ - daemon.parent = self - self.daemons[daemon.name] = daemon - daemon.order_incoming_event.listen(self.place_order, daemon.name) - - def log(self, order, message): - msg = '%s/%s: %s' % (order.get_service_name(), - order.get_id(), - message) - self.logger.info(msg) - - def get_order_db(self): - return self.order_db - - def _update_order_status(self, order): - remaining = self.order_db.count_tasks(order_id = order.id, - closed = None) - if remaining == 0: - total = self.order_db.count_tasks(order_id = order.id) - if total == 1: - task = self.order_db.get_task(order_id = order.id) - order.set_description(task.get_name()) - order.close() - self.set_order_status(order, 'completed') - for logger in self.loggers.pop(order.get_id(), []): - self._free_logger(logger) - - def _on_task_changed(self, task): - self.order_db.save_task(task) - - def create_task(self, order, name): - task = Task(order.id, name) - task.changed_event.listen(self._on_task_changed) - self.order_db.save_task(task) - return task - - def set_order_status(self, order, status): - order.status = status - self.order_db.save_order(order) - self.log(order, 'Status is now "%s"' % status) - - def place_order(self, order, daemon_name): - self.logger.debug('Incoming order from ' + daemon_name) - - # Store it in the database. - self.set_order_status(order, 'incoming') - - # Loop the requested service up. - service = self.services.get(order.get_service_name()) - if not service: - order.close() - self.set_order_status(order, 'service-not-found') - return - - # Notify the service of the new order. - try: - accepted = service.check(order) - except Exception, e: - self.log(order, 'Exception: %s' % e) - order.close() - self.set_order_status(order, 'error') - raise - - if not accepted: - order.close() - self.set_order_status(order, 'rejected') - return - self.set_order_status(order, 'accepted') - - # Save the order, including the data that was passed. - # For performance reasons, use a new thread. - func = _AsyncFunction(self._enter_order, service, order) - func.start() - - def _enter_order(self, service, order): - # Note: This method is called asynchronously. - # Store the order in the database. - self.set_order_status(order, 'saving') - self.order_db.save_order(order) - - self.set_order_status(order, 'starting') - with self.lock: - # We must stop the queue while new jobs are placed, - # else the queue might start processing a job before - # it was attached to a task. - for queue in self.queues.itervalues(): - queue.workqueue.pause() - - try: - service.enter(order) - except Exception, e: - self.log(order, 'Exception: %s' % e) - order.close() - self.set_order_status(order, 'error') - raise - finally: - # Re-enable the workqueue. - for queue in self.queues.itervalues(): - queue.workqueue.unpause() - self.set_order_status(order, 'running') - - # If the service did not enqueue anything, it may already be completed. - self._update_order_status(order) diff --git a/src/Exscriptd/HTTPDaemon.py b/src/Exscriptd/HTTPDaemon.py deleted file mode 100644 index 1182fa86..00000000 --- a/src/Exscriptd/HTTPDaemon.py +++ /dev/null @@ -1,242 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import os -import json -import logging -from traceback import format_exc -from urlparse import parse_qs -from lxml import etree -from Exscript import Host -from Exscript.servers.HTTPd import HTTPd, RequestHandler -from Exscript.util.event import Event -from Exscriptd.Order import Order - -""" -URL list: - - Path Method Function - order/ POST Place an XML formatted order - order/get/?id=1234 GET Returns order 1234 - order/status/?id=1234 GET Status and progress for order 1234 - order/count/ GET Get the total number of orders - order/count/?service=grabber GET Number of orders matching the name - order/list/?offset=10&limit=25 GET Get a list of orders - order/list/?service=grabber GET Filter list of orders by service name - order/list/?description=foobar GET Filter list of orders by description - order/list/?status=completed GET Filter list of orders by status - task/get/?id=1234 GET Returns task 1234 - task/count/?order_id=1234 GET Get the number of tasks for order 1234 - task/list/?order_id=1234 GET Get a list of tasks for order 1234 - log/?task_id=4567 GET Returns the content of the logfile - trace/?task_id=4567 GET Returns the content of the trace file - services/ GET Service overview (not implemented) - services/foo/ GET Get info for the "foo" service (not implemented) - -To test with curl: - - curl --digest --user exscript-http:exscript-http --data @postorder localhost:8123/order/ -""" - -class HTTPHandler(RequestHandler): - def get_response(self): - data = parse_qs(self.data) - logger = self.daemon.logger - order_db = self.daemon.parent.get_order_db() - - if self.path == '/order/': - logger.debug('Parsing order from HTTP request.') - order = Order.from_xml(data['xml'][0]) - logger.debug('XML order parsed complete.') - self.daemon.order_incoming_event(order) - return 'application/json', json.dumps(order.get_id()) - - elif self.path == '/order/get/': - id = int(self.args.get('id')) - order = order_db.get_order(id = id) - return order.toxml() - - elif self.path == '/order/count/': - order_id = self.args.get('order_id') - service = self.args.get('service') - descr = self.args.get('description') - status = self.args.get('status') - created_by = self.args.get('created_by') - n_orders = order_db.count_orders(id = order_id, - service = service, - description = descr, - status = status, - created_by = created_by) - return 'application/json', json.dumps(n_orders) - - elif self.path == '/order/status/': - order_id = int(self.args['id']) - order = order_db.get_order(id = order_id) - progress = order_db.get_order_progress_from_id(order_id) - if not order: - raise Exception('no such order id') - closed = order.get_closed_timestamp() - if closed is not None: - closed = str(closed) - response = {'status': order.get_status(), - 'progress': progress, - 'closed': closed} - return 'application/json', json.dumps(response) - - elif self.path == '/order/list/': - # Fetch the orders. - offset = int(self.args.get('offset', 0)) - limit = min(100, int(self.args.get('limit', 100))) - order_id = self.args.get('order_id') - service = self.args.get('service') - descr = self.args.get('description') - status = self.args.get('status') - created_by = self.args.get('created_by') - orders = order_db.get_orders(id = order_id, - service = service, - description = descr, - status = status, - created_by = created_by, - offset = offset, - limit = limit) - - # Assemble an XML document containing the orders. - xml = etree.Element('xml') - for order in orders: - xml.append(order.toetree()) - return etree.tostring(xml, pretty_print = True) - - elif self.path == '/task/get/': - id = int(self.args.get('id')) - task = order_db.get_task(id = id) - return task.toxml() - - elif self.path == '/task/count/': - order_id = self.args.get('order_id') - if order_id: - n_tasks = order_db.count_tasks(order_id = int(order_id)) - else: - n_tasks = order_db.count_tasks() - return 'application/json', json.dumps(n_tasks) - - elif self.path == '/task/list/': - # Fetch the tasks. - order_id = int(self.args.get('order_id')) - offset = int(self.args.get('offset', 0)) - limit = min(100, int(self.args.get('limit', 100))) - tasks = order_db.get_tasks(order_id = order_id, - offset = offset, - limit = limit) - - # Assemble an XML document containing the orders. - xml = etree.Element('xml') - for task in tasks: - xml.append(task.toetree()) - return etree.tostring(xml, pretty_print = True) - - elif self.path == '/log/': - task_id = int(self.args.get('task_id')) - task = order_db.get_task(id = task_id) - filename = task.get_logfile() - if filename and os.path.isfile(filename): - with open(filename) as file: - return file.read() - else: - return '' - - elif self.path == '/trace/': - task_id = int(self.args.get('task_id')) - task = order_db.get_task(id = task_id) - filename = task.get_tracefile() - if filename and os.path.isfile(filename): - with open(filename) as file: - return file.read() - else: - return '' - - else: - raise Exception('no such API call') - - def handle_POST(self): - self.daemon = self.server.user_data - self.daemon.logger.debug('Receiving HTTP request.') - try: - response = self.get_response() - except Exception, e: - tb = format_exc() - print tb - self.send_response(500) - self.end_headers() - self.wfile.write(tb.encode('utf8')) - self.daemon.logger.error('Exception: %s' % tb) - else: - self.send_response(200) - try: - mime_type, response = response - except ValueError: - self.daemon.logger.debug('Sending HTTP/text response.') - else: - self.daemon.logger.debug('Sending HTTP/json response.') - self.send_header('Content-type', mime_type) - self.end_headers() - self.wfile.write(response) - self.daemon.logger.debug('HTTP call complete.') - - def handle_GET(self): - self.handle_POST() - - def log_message(self, format, *args): - daemon = self.server.user_data - daemon.logger.info(self.address_string() + ' - ' + format % args) - -class HTTPDaemon(object): - def __init__(self, - parent, - name, - logger, - address = '', - port = 80): - self.parent = parent - self.name = name - self.logger = logger - self.order_incoming_event = Event() - self.address = address - self.port = port - addr = self.address, self.port - self.server = HTTPd(addr, HTTPHandler, self) - self.parent.daemon_added(self) - - def log(self, order, message, level = logging.INFO): - msg = '%s/%s/%s: %s' % (self.name, - order.get_service_name(), - order.get_id(), - message) - self.logger.log(level, msg) - - def add_account(self, account): - user = account.get_name() - password = account.get_password() - self.server.add_account(user, password) - - def run(self): - address = (self.address or '*') + ':' + str(self.port) - nameaddr = self.name, address - self.logger.info('HTTPDaemon %s/%s starting.' % nameaddr) - try: - self.logger.info('HTTPDaemon %s/%s listening' % nameaddr) - self.server.serve_forever() - except KeyboardInterrupt: - print '^C received, shutting down server' - self.logger.info('Shutting down normally.') - self.server.socket.close() diff --git a/src/Exscriptd/Order.py b/src/Exscriptd/Order.py deleted file mode 100644 index 418554b6..00000000 --- a/src/Exscriptd/Order.py +++ /dev/null @@ -1,326 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" -Represents a call to a service. -""" -import os -from getpass import getuser -from datetime import datetime -from tempfile import NamedTemporaryFile -from lxml import etree -from Exscript.util.file import get_hosts_from_csv -from Exscriptd.DBObject import DBObject -from Exscriptd.xml import add_hosts_to_etree - -class Order(DBObject): - """ - An order includes all information that is required to make a - service call. - """ - - def __init__(self, service_name): - """ - Constructor. The service_name defines the service to whom - the order is delivered. In other words, this is the - service name is assigned in the config file of the server. - - @type service_name: str - @param service_name: The service that handles the order. - """ - DBObject.__init__(self) - self.id = None - self.status = 'new' - self.service = service_name - self.descr = '' - self.created = datetime.utcnow() - self.closed = None - self.progress = .0 - self.created_by = getuser() - self.xml = etree.Element('order', service = self.service) - - def __repr__(self): - return "" % (self.id, self.service, self.status) - - @staticmethod - def from_etree(order_node): - """ - Creates a new instance by parsing the given XML. - - @type order_node: lxml.etree.Element - @param order_node: The order node of an etree. - @rtype: Order - @return: A new instance of an order. - """ - # Parse required attributes. - descr_node = order_node.find('description') - order_id = order_node.get('id') - order = Order(order_node.get('service')) - order.xml = order_node - order.id = order_id is not None and int(order_id) or None - order.status = order_node.get('status', order.status) - order.created_by = order_node.get('created-by', order.created_by) - created = order_node.get('created') - closed = order_node.get('closed') - progress = order_node.get('progress') - if descr_node is not None: - order.descr = descr_node.text - if created: - created = created.split('.', 1)[0] - created = datetime.strptime(created, "%Y-%m-%d %H:%M:%S") - order.created = created - if closed: - closed = closed.split('.', 1)[0] - closed = datetime.strptime(closed, "%Y-%m-%d %H:%M:%S") - order.closed = closed - if progress is not None: - order.progress = float(progress) - return order - - @staticmethod - def from_xml(xml): - """ - Creates a new instance by parsing the given XML. - - @type xml: str - @param xml: A string containing an XML formatted order. - @rtype: Order - @return: A new instance of an order. - """ - xml = etree.fromstring(xml) - return Order.from_etree(xml.find('order')) - - @staticmethod - def from_xml_file(filename): - """ - Creates a new instance by reading the given XML file. - - @type filename: str - @param filename: A file containing an XML formatted order. - @rtype: Order - @return: A new instance of an order. - """ - # Parse required attributes. - xml = etree.parse(filename) - return Order.from_etree(xml.find('order')) - - @staticmethod - def from_csv_file(service, filename, encoding = 'utf-8'): - """ - Creates a new instance by reading the given CSV file. - - @type service: str - @param service: The service name. - @type filename: str - @param filename: A file containing a CSV formatted list of hosts. - @type encoding: str - @param encoding: The name of the encoding. - @rtype: Order - @return: A new instance of an order. - """ - order = Order(service) - hosts = get_hosts_from_csv(filename, encoding = encoding) - add_hosts_to_etree(order.xml, hosts) - return order - - def toetree(self): - """ - Returns the order as an lxml etree. - - @rtype: lxml.etree - @return: The resulting tree. - """ - if self.id: - self.xml.attrib['id'] = str(self.id) - if self.status: - self.xml.attrib['status'] = str(self.status) - if self.created: - self.xml.attrib['created'] = str(self.created) - if self.closed: - self.xml.attrib['closed'] = str(self.closed) - if self.progress: - self.xml.attrib['progress'] = str(self.progress) - if self.descr: - etree.SubElement(self.xml, 'description').text = str(self.descr) - if self.created_by: - self.xml.attrib['created-by'] = str(self.created_by) - return self.xml - - def toxml(self, pretty = True): - """ - Returns the order as an XML formatted string. - - @type pretty: bool - @param pretty: Whether to format the XML in a human readable way. - @rtype: str - @return: The XML representing the order. - """ - xml = etree.Element('xml') - order = self.toetree() - xml.append(order) - return etree.tostring(xml, pretty_print = pretty) - - def todict(self): - """ - Returns the order's attributes as one flat dictionary. - - @rtype: dict - @return: A dictionary representing the order. - """ - values = dict(service = self.get_service_name(), - status = self.get_status(), - description = self.get_description(), - progress = self.get_progress(), - created = self.get_created_timestamp(), - closed = self.get_closed_timestamp(), - created_by = self.get_created_by()) - if self.id: - values['id'] = self.get_id() - return values - - def write(self, thefile): - """ - Export the order as an XML file. - - @type thefile: str or file object - @param thefile: XML - """ - if hasattr(thefile, 'write'): - thefile.write(self.toxml()) - return - - dirname = os.path.dirname(thefile) - with NamedTemporaryFile(dir = dirname, - prefix = '.', - delete = False) as tmpfile: - tmpfile.write(self.toxml()) - tmpfile.flush() - os.chmod(tmpfile.name, 0644) - os.rename(tmpfile.name, thefile) - - def is_valid(self): - """ - Returns True if the order validates, False otherwise. - - @rtype: bool - @return: True if the order is valid, False otherwise. - """ - return True #FIXME - - def get_id(self): - """ - Returns the order id. - - @rtype: str - @return: The id of the order. - """ - return self.id - - def set_service_name(self, name): - """ - Set the name of the service that is ordered. - - @type name: str - @param name: The service name. - """ - self.service = name - - def get_service_name(self): - """ - Returns the name of the service that is ordered. - - @rtype: str - @return: The service name. - """ - return self.service - - def get_status(self): - """ - Returns the order status. - - @rtype: str - @return: The order status. - """ - return self.status - - def set_description(self, description): - """ - Sets a freely defined description on the order. - - @type description: str - @param description: The new description. - """ - self.descr = description and str(description) or '' - - def get_description(self): - """ - Returns the description of the order. - - @rtype: str - @return: The description. - """ - return self.descr - - def get_created_timestamp(self): - """ - Returns the time at which the order was created. - - @rtype: datetime.datetime - @return: The timestamp. - """ - return self.created - - def get_closed_timestamp(self): - """ - Returns the time at which the order was closed, or None if the - order is still open. - - @rtype: datetime.datetime|None - @return: The timestamp or None. - """ - return self.closed - - def get_created_by(self): - """ - Returns the username of the user who opened the order. Defaults - to whatever getpass.getuser() returns. - - @rtype: str - @return: The value of the 'created-by' field. - """ - return self.created_by - - def get_progress(self): - """ - Returns the progress of the order. - - @rtype: float|None - @return: The progress (1.0 is max). - """ - return self.progress - - def get_progress_percent(self): - """ - Returns the progress as a string, in percent. - - @rtype: str - @return: The progress in percent. - """ - return '%.1f' % (self.progress * 100.0) - - def close(self): - """ - Marks the order closed. - """ - self.closed = datetime.utcnow() diff --git a/src/Exscriptd/OrderDB.py b/src/Exscriptd/OrderDB.py deleted file mode 100644 index 272604a5..00000000 --- a/src/Exscriptd/OrderDB.py +++ /dev/null @@ -1,579 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -from datetime import datetime -import sqlalchemy as sa -from Exscript.util.cast import to_list -from Exscript.util.impl import synchronized -from Exscriptd.Order import Order -from Exscriptd.Task import Task - -# Note: The synchronized decorator is used because -# sqlite does not support concurrent writes, so we need to -# do this to have graceful locking (rather than sqlite's -# hard locking). - -class OrderDB(object): - """ - The main interface for accessing the database. - """ - - def __init__(self, engine): - """ - Instantiates a new OrderDB. - - @type engine: object - @param engine: An sqlalchemy database engine. - @rtype: OrderDB - @return: The new instance. - """ - self.engine = engine - self.metadata = sa.MetaData(self.engine) - self._table_prefix = 'exscriptd_' - self._table_map = {} - self.__update_table_names() - - def __add_table(self, table): - """ - Adds a new table to the internal table list. - - @type table: Table - @param table: An sqlalchemy table. - """ - pfx = self._table_prefix - self._table_map[table.name[len(pfx):]] = table - - def __update_table_names(self): - """ - Adds all tables to the internal table list. - """ - pfx = self._table_prefix - self.__add_table(sa.Table(pfx + 'order', self.metadata, - sa.Column('id', sa.Integer, primary_key = True), - sa.Column('service', sa.String(50), index = True), - sa.Column('status', sa.String(20), index = True), - sa.Column('description', sa.String(150)), - sa.Column('created', sa.DateTime, default = datetime.utcnow), - sa.Column('closed', sa.DateTime), - sa.Column('created_by', sa.String(50)), - mysql_engine = 'INNODB' - )) - - self.__add_table(sa.Table(pfx + 'task', self.metadata, - sa.Column('id', sa.Integer, primary_key = True), - sa.Column('order_id', sa.Integer, index = True), - sa.Column('job_id', sa.String(33), index = True), - sa.Column('name', sa.String(150), index = True), - sa.Column('status', sa.String(150), index = True), - sa.Column('progress', sa.Float, default = 0.0), - sa.Column('started', sa.DateTime, default = datetime.utcnow), - sa.Column('closed', sa.DateTime, index = True), - sa.Column('logfile', sa.String(250)), - sa.Column('tracefile', sa.String(250)), - sa.Column('vars', sa.PickleType()), - sa.ForeignKeyConstraint(['order_id'], [pfx + 'order.id'], ondelete = 'CASCADE'), - mysql_engine = 'INNODB' - )) - - - @synchronized - def install(self): - """ - Installs (or upgrades) database tables. - - @rtype: Boolean - @return: True on success, False otherwise. - """ - self.metadata.create_all() - return True - - @synchronized - def uninstall(self): - """ - Drops all tables from the database. Use with care. - - @rtype: Boolean - @return: True on success, False otherwise. - """ - self.metadata.drop_all() - return True - - @synchronized - def clear_database(self): - """ - Drops the content of any database table used by this library. - Use with care. - - Wipes out everything, including types, actions, resources and acls. - - @rtype: Boolean - @return: True on success, False otherwise. - """ - delete = self._table_map['order'].delete() - delete.execute() - return True - - def debug(self, debug = True): - """ - Enable/disable debugging. - - @type debug: Boolean - @param debug: True to enable debugging. - """ - self.engine.echo = debug - - def set_table_prefix(self, prefix): - """ - Define a string that is prefixed to all table names in the database. - Default is 'guard_'. - - @type prefix: string - @param prefix: The new prefix. - """ - self._table_prefix = prefix - self.__update_table_names() - - def get_table_prefix(self): - """ - Returns the current database table prefix. - - @rtype: string - @return: The current prefix. - """ - return self._table_prefix - - @synchronized - def __add_task(self, task): - """ - Inserts the given task into the database. - """ - if task is None: - raise AttributeError('task argument must not be None') - if task.order_id is None: - raise AttributeError('order_id must not be None') - - if not task.is_dirty(): - return - - # Insert the task - insert = self._table_map['task'].insert() - result = insert.execute(**task.todict()) - task_id = result.inserted_primary_key[0] - - task.untouch() - return task_id - - @synchronized - def __save_task(self, task): - """ - Inserts or updates the given task into the database. - """ - if task is None: - raise AttributeError('task argument must not be None') - if task.order_id is None: - raise AttributeError('order_id must not be None') - - if not task.is_dirty(): - return - - # Insert or update the task. - tbl_t = self._table_map['task'] - fields = task.todict() - if task.id is None: - query = tbl_t.insert() - result = query.execute(**fields) - task.id = result.inserted_primary_key[0] - else: - query = tbl_t.update(tbl_t.c.id == task.id) - result = query.execute(**fields) - - task.untouch() - return task.id - - def __get_task_from_row(self, row): - assert row is not None - tbl_t = self._table_map['task'] - task = Task(row[tbl_t.c.order_id], row[tbl_t.c.name]) - task.id = row[tbl_t.c.id] - task.job_id = row[tbl_t.c.job_id] - task.status = row[tbl_t.c.status] - task.progress = row[tbl_t.c.progress] - task.started = row[tbl_t.c.started] - task.closed = row[tbl_t.c.closed] - task.logfile = row[tbl_t.c.logfile] - task.tracefile = row[tbl_t.c.tracefile] - task.vars = row[tbl_t.c.vars] - task.untouch() - return task - - def __get_tasks_from_query(self, query): - """ - Returns a list of tasks. - """ - assert query is not None - result = query.execute() - return [self.__get_task_from_row(row) for row in result] - - def __get_tasks_cond(self, **kwargs): - tbl_t = self._table_map['task'] - - # Search conditions. - where = None - for field in ('id', - 'order_id', - 'job_id', - 'name', - 'status', - 'opened', - 'closed'): - if field in kwargs: - cond = None - for value in to_list(kwargs.get(field)): - cond = sa.or_(cond, tbl_t.c[field] == value) - where = sa.and_(where, cond) - - return where - - def __get_tasks_query(self, fields, offset, limit, **kwargs): - tbl_t = self._table_map['task'] - where = self.__get_tasks_cond(**kwargs) - return sa.select(fields, - where, - from_obj = [tbl_t], - order_by = [sa.desc(tbl_t.c.id)], - offset = offset, - limit = limit) - - def __get_orders_cond(self, **kwargs): - tbl_o = self._table_map['order'] - - # Search conditions. - where = None - for field in ('id', 'service', 'description', 'status', 'created_by'): - values = kwargs.get(field) - if values is not None: - cond = None - for value in to_list(values): - cond = sa.or_(cond, tbl_o.c[field].like(value)) - where = sa.and_(where, cond) - - return where - - def __get_orders_query(self, offset = 0, limit = None, **kwargs): - tbl_o = self._table_map['order'] - tbl_t = self._table_map['task'] - where = self.__get_orders_cond(**kwargs) - fields = list(tbl_o.c) - table = tbl_o.outerjoin(tbl_t, tbl_t.c.order_id == tbl_o.c.id) - fields.append(sa.func.avg(tbl_t.c.progress).label('avg_progress')) - return sa.select(fields, - where, - from_obj = [table], - group_by = [tbl_o.c.id], - order_by = [sa.desc(tbl_o.c.id)], - offset = offset, - limit = limit) - - @synchronized - def __add_order(self, order): - """ - Inserts the given order into the database. - """ - if order is None: - raise AttributeError('order argument must not be None') - - # Insert the order - insert = self._table_map['order'].insert() - fields = dict(k for k in order.todict().iteritems() - if k[0] not in ('id', 'created', 'progress')) - result = insert.execute(**fields) - order.id = result.inserted_primary_key[0] - return order.id - - @synchronized - def __save_order(self, order): - """ - Updates the given order in the database. Does nothing if the - order is not yet in the database. - - @type order: Order - @param order: The order to be saved. - """ - if order is None: - raise AttributeError('order argument must not be None') - - # Check if the order already exists. - if order.id: - theorder = self.get_order(id = order.get_id()) - else: - theorder = None - - # Insert or update it. - if not theorder: - return self.add_order(order) - table = self._table_map['order'] - fields = dict(k for k in order.todict().iteritems() - if k[0] not in ('id', 'created', 'progress')) - query = table.update(table.c.id == order.get_id()) - query.execute(**fields) - - def __get_order_from_row(self, row): - assert row is not None - tbl_a = self._table_map['order'] - order = Order(row[tbl_a.c.service]) - order.id = row[tbl_a.c.id] - order.status = row[tbl_a.c.status] - order.created = row[tbl_a.c.created] - order.closed = row[tbl_a.c.closed] - order.created_by = row[tbl_a.c.created_by] - order.set_description(row[tbl_a.c.description]) - try: - order.progress = float(row.avg_progress) - except TypeError: # Order has no tasks - if order.closed: - order.progress = 1.0 - else: - order.progress = .0 - return order - - def __get_orders_from_query(self, query): - """ - Returns a list of orders. - """ - assert query is not None - result = query.execute() - return [self.__get_order_from_row(row) for row in result] - - def count_orders(self, **kwargs): - """ - Returns the total number of orders matching the given criteria. - - @rtype: int - @return: The number of orders. - @type kwargs: dict - @param kwargs: For a list of allowed keys see get_orders(). - """ - tbl_o = self._table_map['order'] - where = self.__get_orders_cond(**kwargs) - return tbl_o.count(where).execute().fetchone()[0] - - def get_order(self, **kwargs): - """ - Like get_orders(), but - - Returns None, if no match was found. - - Returns the order, if exactly one match was found. - - Raises an error if more than one match was found. - - @type kwargs: dict - @param kwargs: For a list of allowed keys see get_orders(). - @rtype: Order - @return: The order or None. - """ - result = self.get_orders(0, 2, **kwargs) - if len(result) == 0: - return None - elif len(result) > 1: - raise IndexError('Too many results') - return result[0] - - def get_orders(self, offset = 0, limit = None, **kwargs): - """ - Returns all orders that match the given criteria. - - @type offset: int - @param offset: The offset of the first item to be returned. - @type limit: int - @param limit: The maximum number of items that is returned. - @type kwargs: dict - @param kwargs: The following keys may be used: - - id - the id of the order (str) - - service - the service name (str) - - description - the order description (str) - - status - the status (str) - All values may also be lists (logical OR). - @rtype: list[Order] - @return: The list of orders. - """ - select = self.__get_orders_query(avg = True, - offset = offset, - limit = limit, - **kwargs) - return self.__get_orders_from_query(select) - - def add_order(self, orders): - """ - Inserts the given order into the database. - - @type orders: Order|list[Order] - @param orders: The orders to be added. - """ - if orders is None: - raise AttributeError('order argument must not be None') - with self.engine.contextual_connect(close_with_result = True).begin(): - for order in to_list(orders): - self.__add_order(order) - - def close_open_orders(self): - """ - Sets the 'closed' timestamp of all orders that have none, without - changing the status field. - """ - closed = datetime.utcnow() - tbl_o = self._table_map['order'] - tbl_t = self._table_map['task'] - query1 = tbl_t.update(tbl_t.c.closed == None) - query2 = tbl_o.update(tbl_o.c.closed == None) - query1.execute(closed = closed) - query2.execute(closed = closed) - - def save_order(self, orders): - """ - Updates the given orders in the database. Does nothing if - the order doesn't exist. - - @type orders: Order|list[Order] - @param orders: The order to be saved. - """ - if orders is None: - raise AttributeError('order argument must not be None') - - with self.engine.contextual_connect(close_with_result = True).begin(): - for order in to_list(orders): - self.__save_order(order) - - def get_order_progress_from_id(self, id): - """ - Returns the progress of the order in percent. - - @type id: int - @param id: The id of the order. - @rtype: float - @return: A float between 0.0 and 1.0 - """ - order = self.get_order(id = id) - return order.get_progress() - - def count_tasks(self, **kwargs): - """ - Returns the number of matching tasks in the DB. - - @type kwargs: dict - @param kwargs: See L{get_tasks()}. - @rtype: int - @return: The number of tasks. - """ - tbl_t = self._table_map['task'] - where = self.__get_tasks_cond(**kwargs) - query = tbl_t.count(where) - return query.execute().fetchone()[0] - - def get_task(self, **kwargs): - """ - Like get_tasks(), but - - Returns None, if no match was found. - - Returns the task, if exactly one match was found. - - Raises an error if more than one match was found. - - @type kwargs: dict - @param kwargs: For a list of allowed keys see get_tasks(). - @rtype: Task - @return: The task or None. - """ - result = self.get_tasks(0, 2, **kwargs) - if len(result) == 0: - return None - elif len(result) > 1: - raise IndexError('Too many results') - return result[0] - - def get_tasks(self, offset = 0, limit = None, **kwargs): - """ - Returns all tasks that match the given criteria. - - @type offset: int - @param offset: The offset of the first item to be returned. - @type limit: int - @param limit: The maximum number of items that is returned. - @type kwargs: dict - @param kwargs: The following keys may be used: - - id - the id of the task (int) - - order_id - the order id of the task (int) - - job_id - the job id of the task (str) - - name - the name (str) - - status - the status (str) - All values may also be lists (logical OR). - @rtype: list[Task] - @return: The list of tasks. - """ - tbl_t = self._table_map['task'] - fields = list(tbl_t.c) - query = self.__get_tasks_query(fields, offset, limit, **kwargs) - return self.__get_tasks_from_query(query) - - def save_task(self, task): - """ - Inserts or updates the given task in the database. - - @type order: Order - @param order: The order for which a task is added. - @type task: Task - @param task: The task to be saved. - """ - if task is None: - raise AttributeError('task argument must not be None') - if task.order_id is None: - raise AttributeError('order id must not be None') - - if not task.is_dirty(): - return - - return self.__save_task(task) - - @synchronized - def mark_tasks(self, new_status, offset = 0, limit = None, **kwargs): - """ - Returns all tasks that match the given criteria and changes - their status to the given value. - - @type new_status: str - @param new_status: The new status. - @type offset: int - @param offset: The offset of the first item to be returned. - @type limit: int - @param limit: The maximum number of items that is returned. - @type kwargs: dict - @param kwargs: See L{get_tasks()}. - @rtype: list[Task] - @return: The list of tasks. - """ - tbl_t = self._table_map['task'] - - # Find the ids of the matching tasks. - where = self.__get_tasks_cond(**kwargs) - id_select = sa.select([tbl_t.c.id], - where, - from_obj = [tbl_t], - order_by = [tbl_t.c.id], - offset = offset, - limit = limit) - id_list = [row.id for row in id_select.execute()] - - # Update the status of those tasks. - query = tbl_t.update(tbl_t.c.id.in_(id_list)) - result = query.execute(status = new_status) - - # Now create a Task object for each of those tasks. - all_select = tbl_t.select(tbl_t.c.id.in_(id_list), - order_by = [tbl_t.c.id]) - return self.__get_tasks_from_query(all_select) diff --git a/src/Exscriptd/Service.py b/src/Exscriptd/Service.py deleted file mode 100644 index 2abd21e5..00000000 --- a/src/Exscriptd/Service.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import __builtin__ -import sys -import os -from Exscriptd.util import find_module_recursive -from Exscriptd.ConfigReader import ConfigReader - -class Service(object): - def __init__(self, - parent, - name, - module, - cfg_dir, - main_cfg, - queue_name = None): - self.parent = parent - self.name = name - self.cfg_dir = cfg_dir - self.main_cfg = main_cfg - self.queue_name = queue_name - self.parent.service_added(self) - - try: - fp, filename, description = find_module_recursive(module) - except ImportError: - raise Exception('invalid module name: %s' % module) - filename = os.path.join(filename, 'service.py') - with open(filename) as file: - content = file.read() - code = compile(content, filename, 'exec') - self.vars = {} - self.vars['__builtin__'] = __builtin__ - self.vars['__file__'] = filename - self.vars['__module__'] = module - self.vars['__service__'] = self - self.vars['__exscriptd__'] = parent - self.vars['__main_cfg__'] = self.main_cfg - - # Load the module using evil path manipulation, but oh well... - # can't think of a sane way to do this. - sys.path.insert(0, os.path.dirname(filename)) - result = eval(code, self.vars) - sys.path.pop(0) - - self.check_func = self.vars.get('check') - self.enter_func = self.vars.get('enter') - - if not self.enter_func: - msg = filename + ': required function enter() not found.' - raise Exception(msg) - - def get_queue_name(self): - return self.queue_name - - def read_config(self, name, parser = ConfigReader): - filename = os.path.join(self.cfg_dir, name) - return parser(filename, parent = self.main_cfg) - - def check(self, order): - if self.check_func: - return self.check_func(order) - return True - - def enter(self, order): - return self.enter_func(order) - - def run_function(self, name, *args): - return self.vars.get(name)(*args) diff --git a/src/Exscriptd/Task.py b/src/Exscriptd/Task.py deleted file mode 100644 index fcb29864..00000000 --- a/src/Exscriptd/Task.py +++ /dev/null @@ -1,328 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" -Represents an activity within an order. -""" -import os -import sys -from datetime import datetime -from lxml import etree -from Exscriptd.DBObject import DBObject -from Exscript.util.event import Event - -class Task(DBObject): - def __init__(self, order_id, name): - DBObject.__init__(self) - self.id = None - self.order_id = order_id - self.job_id = None # reference to Exscript.workqueue.Job.id - self.name = name - self.status = 'new' - self.progress = .0 - self.started = datetime.utcnow() - self.closed = None - self.logfile = None - self.tracefile = None - self.vars = {} - self.changed_event = Event() - - @staticmethod - def from_etree(task_node): - """ - Creates a new instance by parsing the given XML. - - @type task_node: lxml.etree.Element - @param task_node: The task node of an etree. - @rtype: Task - @return: A new instance of an task. - """ - # Parse required attributes. - name = task_node.find('name').text - order_id = task_node.get('order_id') - task = Task(order_id, name) - task.id = int(task_node.get('id')) - task.status = task_node.find('status').text - task.progress = float(task_node.find('progress').text) - started_node = task_node.find('started') - closed_node = task_node.find('closed') - logfile_node = task_node.find('logfile') - tracefile_node = task_node.find('tracefile') - if started_node is not None: - started = started_node.text.split('.', 1)[0] - started = datetime.strptime(started, "%Y-%m-%d %H:%M:%S") - task.started = started - if closed_node is not None: - closed = closed_node.text.split('.', 1)[0] - closed = datetime.strptime(closed, "%Y-%m-%d %H:%M:%S") - task.closed = closed - if logfile_node is not None: - task.logfile = logfile_node.text - if tracefile_node is not None: - task.tracefile = tracefile_node.text - return task - - @staticmethod - def from_xml(xml): - """ - Creates a new instance by parsing the given XML. - - @type xml: str - @param xml: A string containing an XML formatted task. - @rtype: Task - @return: A new instance of an task. - """ - xml = etree.fromstring(xml) - return Task.from_etree(xml.find('task')) - - def toetree(self): - """ - Returns the task as an lxml etree. - - @rtype: lxml.etree - @return: The resulting tree. - """ - task = etree.Element('task', - id = str(self.id), - order_id = str(self.order_id)) - etree.SubElement(task, 'name').text = str(self.name) - etree.SubElement(task, 'status').text = str(self.status) - etree.SubElement(task, 'progress').text = str(self.progress) - if self.started: - etree.SubElement(task, 'started').text = str(self.started) - if self.closed: - etree.SubElement(task, 'closed').text = str(self.closed) - if self.logfile: - etree.SubElement(task, 'logfile').text = str(self.logfile) - if self.tracefile: - etree.SubElement(task, 'tracefile').text = str(self.tracefile) - return task - - def toxml(self, pretty = True): - """ - Returns the task as an XML formatted string. - - @type pretty: bool - @param pretty: Whether to format the XML in a human readable way. - @rtype: str - @return: The XML representing the task. - """ - xml = etree.Element('xml') - task = self.toetree() - xml.append(task) - return etree.tostring(xml, pretty_print = pretty) - - def todict(self): - result = dict(order_id = self.order_id, - job_id = self.get_job_id(), - name = self.get_name(), - status = self.get_status(), - progress = self.get_progress(), - started = self.get_started_timestamp(), - closed = self.get_closed_timestamp(), - logfile = self.get_logfile(), - tracefile = self.get_tracefile(), - vars = self.vars) - if self.id is not None: - result['id'] = self.id - return result - - def get_id(self): - """ - Returns the task id. - - @rtype: str - @return: The id of the task. - """ - return self.id - - def set_job_id(self, job_id): - """ - Associate the task with the Exscript.workqueue.Job with the given - id. - - @type job_id: int - @param job_id: The id of the job. - """ - self.touch() - self.job_id = job_id - self.set_status('queued') - - def get_job_id(self): - """ - Returns the associated Exscript.workqueue.Job, or None. - - @type job_id: str - @param job_id: The id of the task. - """ - return self.job_id - - def set_name(self, name): - """ - Change the task name. - - @type name: string - @param name: A human readable name. - """ - self.touch() - self.name = name - - def get_name(self): - """ - Returns the current name as a string. - - @rtype: string - @return: A human readable name. - """ - return self.name - - def set_status(self, status): - """ - Change the current status. - - @type status: string - @param status: A human readable status. - """ - self.touch() - self.status = status - self.changed_event(self) - - def get_status(self): - """ - Returns the current status as a string. - - @rtype: string - @return: A human readable status. - """ - return self.status - - def set_progress(self, progress): - """ - Change the current progress. - - @type progress: float - @param progress: The new progress. - """ - self.touch() - self.progress = progress - - def get_progress(self): - """ - Returns the progress as a float between 0.0 and 1.0. - - @rtype: float - @return: The progress. - """ - return self.progress - - def get_progress_percent(self): - """ - Returns the progress as a string, in percent. - - @rtype: str - @return: The progress in percent. - """ - return '%.1f' % (self.progress * 100.0) - - def get_started_timestamp(self): - """ - Returns the time at which the task was started. - - @rtype: datetime.datetime - @return: The timestamp. - """ - return self.started - - def close(self, status = None): - """ - Marks the task closed. - - @type status: string - @param status: A human readable status, or None to leave unchanged. - """ - self.touch() - self.closed = datetime.utcnow() - if status: - self.set_status(status) - - def completed(self): - """ - Like close(), but sets the status to 'completed' and the progress - to 100%. - """ - self.close('completed') - self.set_progress(1.0) - - def get_closed_timestamp(self): - """ - Returns the time at which the task was closed, or None if the - task is still open. - - @rtype: datetime.datetime|None - @return: The timestamp or None. - """ - return self.closed - - def set_logfile(self, *logfile): - """ - Set the name of the logfile, and set the name of the tracefile - to the same name with '.error' appended. - - @type logfile: string - @param logfile: A filename. - """ - self.touch() - self.logfile = os.path.join(*logfile) - self.tracefile = self.logfile + '.error' - - def get_logfile(self): - """ - Returns the name of the logfile as a string. - - @rtype: string|None - @return: A filename, or None. - """ - return self.logfile - - def set_tracefile(self, tracefile): - """ - Set the name of the tracefile. - - @type tracefile: string - @param tracefile: A filename. - """ - self.touch() - self.tracefile = os.path.join(*tracefile) - - def get_tracefile(self): - """ - Returns the name of the tracefile as a string. - - @rtype: string|None - @return: A filename, or None. - """ - return self.tracefile - - def set(self, key, value): - """ - Defines a variable that is carried along with the task. - The value *must* be pickleable. - """ - self.vars[key] = value - - def get(self, key, default = None): - """ - Returns the value as previously defined by L{Task.set()}. - """ - return self.vars.get(key, default) diff --git a/src/Exscriptd/__init__.py b/src/Exscriptd/__init__.py deleted file mode 100644 index d9589ab3..00000000 --- a/src/Exscriptd/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" -The Exscript server -""" -from Exscriptd.ConfigReader import ConfigReader -from Exscriptd.Order import Order -from Exscriptd.Task import Task -from Exscriptd.Client import Client diff --git a/src/Exscriptd/config/AccountPoolConfig.py b/src/Exscriptd/config/AccountPoolConfig.py deleted file mode 100644 index 4f78b61d..00000000 --- a/src/Exscriptd/config/AccountPoolConfig.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright (C) 2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import os -from Exscriptd.Config import Config -from Exscriptd.config.ConfigSection import ConfigSection - -class AccountPoolConfig(ConfigSection): - def __init__(self, *args, **kwargs): - ConfigSection.__init__(self, *args, **kwargs) - self.pool_name = None - self.filename = None - self.config = Config(self.global_options.config_dir, False) - - @staticmethod - def get_description(): - return 'add or configure account pools' - - @staticmethod - def get_commands(): - return (('add', 'add a new account pool'), - ('edit', 'replace an existing account pool')) - - def prepare_add(self, parser, pool_name, filename): - self.pool_name = pool_name - self.filename = filename - if not os.path.isfile(filename): - parser.error('invalid file: ' + filename) - if self.config.has_account_pool(self.pool_name): - parser.error('account pool already exists') - - def start_add(self): - self.config.add_account_pool_from_file(self.pool_name, self.filename) - print 'Account pool added.' - - def prepare_edit(self, parser, pool_name, filename): - self.pool_name = pool_name - self.filename = filename - if not os.path.isfile(filename): - parser.error('invalid file: ' + filename) - if not self.config.has_account_pool(self.pool_name): - parser.error('account pool not found') - - def start_edit(self): - self.config.add_account_pool_from_file(self.pool_name, self.filename) - print 'Account pool configured.' diff --git a/src/Exscriptd/config/BaseConfig.py b/src/Exscriptd/config/BaseConfig.py deleted file mode 100644 index 3329fe77..00000000 --- a/src/Exscriptd/config/BaseConfig.py +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright (C) 2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import os -import stat -import re -from Exscriptd.Config import Config, default_log_dir -from Exscriptd.config.ConfigSection import ConfigSection - -__dirname__ = os.path.dirname(__file__) -spool_dir = os.path.join('/var', 'spool', 'exscriptd') -pidfile = os.path.join('/var', 'run', 'exscriptd.pid') -init_dir = os.path.join('/etc', 'init.d') - -class BaseConfig(ConfigSection): - def __init__(self, *args, **kwargs): - ConfigSection.__init__(self, *args, **kwargs) - self.config = None - - def _read_config(self): - self.config = Config(self.global_options.config_dir, False) - - @staticmethod - def get_description(): - return 'global base configuration' - - @staticmethod - def get_commands(): - return (('install', 'install exscriptd base config'), - ('edit', 'change the base config')) - - def _generate(self, infilename, outfilename): - if not self.options.overwrite and os.path.isfile(outfilename): - self.info('file exists, skipping.\n') - return - - vars = {'@CFG_DIR@': self.global_options.config_dir, - '@LOG_DIR@': self.options.log_dir, - '@SPOOL_DIR@': spool_dir, - '@SCRIPT_DIR@': self.script_dir, - '@PYTHONPATH@': os.environ.get('PYTHONPATH'), - '@PIDFILE@': self.options.pidfile, - '@INIT_DIR@': init_dir} - sub_re = re.compile('(' + '|'.join(vars.keys()) + ')+') - - with open(infilename) as infile: - content = infile.read() - subst = lambda s: vars[s.group(0)] - content = sub_re.sub(subst, content) - with open(outfilename, 'w') as outfile: - outfile.write(content) - self.info('done.\n') - - def getopt_install(self, parser): - self.getopt_edit(parser) - parser.add_option('--overwrite', - dest = 'overwrite', - action = 'store_true', - default = False, - help = 'overwrite existing files') - parser.add_option('--pidfile', - dest = 'pidfile', - metavar = 'STRING', - default = pidfile, - help = 'the location of the pidfile') - - def _make_executable(self, filename): - self.info('making %s executable...\n' % filename) - mode = os.stat(filename).st_mode - os.chmod(filename, mode|stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH) - - def _create_directories(self): - log_dir = self.options.log_dir - self.info('creating log directory %s... ' % log_dir) - self._mkdir(log_dir) - self.info('creating spool directory %s... ' % spool_dir) - self._mkdir(spool_dir) - cfg_dir = self.global_options.config_dir - self.info('creating config directory %s... ' % cfg_dir) - self._mkdir(cfg_dir) - service_dir = os.path.join(cfg_dir, 'services') - self.info('creating service directory %s... ' % service_dir) - self._mkdir(service_dir) - - def start_install(self): - # Install the init script. - init_template = os.path.join(__dirname__, 'exscriptd.in') - init_file = os.path.join('/etc', 'init.d', 'exscriptd') - self.info('creating init-file at %s... ' % init_file) - self._generate(init_template, init_file) - self._make_executable(init_file) - - # Create directories. - self._create_directories() - - # Install the default config file. - cfg_tmpl = os.path.join(__dirname__, 'main.xml.in') - cfg_file = os.path.join(self.global_options.config_dir, 'main.xml') - self.info('creating config file %s... ' % cfg_file) - self._generate(cfg_tmpl, cfg_file) - - def getopt_edit(self, parser): - parser.add_option('--log-dir', - dest = 'log_dir', - default = default_log_dir, - metavar = 'FILE', - help = 'where to place log files') - - def prepare_edit(self, parser): - self._read_config() - cfg_file = os.path.join(self.global_options.config_dir, 'main.xml') - if not os.path.exists(cfg_file): - parser.error('no existing base installation found') - - def start_edit(self): - self._create_directories() - self.config.set_logdir(self.options.log_dir) - print 'Base configuration saved.' diff --git a/src/Exscriptd/config/ConfigSection.py b/src/Exscriptd/config/ConfigSection.py deleted file mode 100644 index 78a3a08f..00000000 --- a/src/Exscriptd/config/ConfigSection.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (C) 2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import os -import sys - -class ConfigSection(object): - def __init__(self, global_options, script_dir): - self.global_options = global_options - self.options = None - self.script_dir = script_dir - - @staticmethod - def get_description(): - raise NotImplementedError() - - @staticmethod - def get_commands(): - raise NotImplementedError() - - def _mkdir(self, dirname): - if os.path.isdir(dirname): - self.info('directory exists, skipping.\n') - else: - os.makedirs(dirname) - self.info('done.\n') - - def info(self, *args): - sys.stdout.write(' '.join(str(a) for a in args)) - - def error(self, *args): - sys.stderr.write(' '.join(str(a) for a in args)) diff --git a/src/Exscriptd/config/DaemonConfig.py b/src/Exscriptd/config/DaemonConfig.py deleted file mode 100644 index 9d4cf442..00000000 --- a/src/Exscriptd/config/DaemonConfig.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright (C) 2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -from Exscriptd.Config import Config -from Exscriptd.config.ConfigSection import ConfigSection - -class DaemonConfig(ConfigSection): - def __init__(self, *args, **kwargs): - ConfigSection.__init__(self, *args, **kwargs) - self.daemon_name = None - self.config = None - - def _read_config(self): - self.config = Config(self.global_options.config_dir, False) - - @staticmethod - def get_description(): - return 'daemon-specific configuration' - - @staticmethod - def get_commands(): - return (('add', 'configure a new daemon'), - ('edit', 'configure an existing daemon')) - - def getopt_add(self, parser): - parser.add_option('--address', - dest = 'address', - metavar = 'STRING', - help = 'the address to listen on, all by default') - parser.add_option('--port', - dest = 'port', - metavar = 'INT', - default = 8132, - help = 'the TCP port number') - parser.add_option('--database', - dest = 'database', - metavar = 'STRING', - help = 'name of the order database') - parser.add_option('--account-pool', - dest = 'account_pool', - metavar = 'STRING', - help = 'the account pool used for authenticating' \ - + 'HTTP clients') - - def prepare_add(self, parser, daemon_name): - self.daemon_name = daemon_name - self._read_config() - if self.config.has_daemon(self.daemon_name): - parser.error('daemon already exists') - - def start_add(self): - self.config.add_daemon(self.daemon_name, - self.options.address, - self.options.port, - self.options.account_pool, - self.options.database) - print 'Daemon added.' - - def getopt_edit(self, parser): - self.getopt_add(parser) - - def prepare_edit(self, parser, daemon_name): - self.daemon_name = daemon_name - self._read_config() - if not self.config.has_daemon(self.daemon_name): - parser.error('daemon not found') - if not self.config.has_database(self.options.database): - parser.error('database not found') - - def start_edit(self): - self.config.add_daemon(self.daemon_name, - self.options.address, - self.options.port, - self.options.account_pool, - self.options.database) - print 'Daemon configured.' diff --git a/src/Exscriptd/config/DatabaseConfig.py b/src/Exscriptd/config/DatabaseConfig.py deleted file mode 100644 index 1c397a3b..00000000 --- a/src/Exscriptd/config/DatabaseConfig.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (C) 2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -from Exscriptd.Config import Config -from Exscriptd.config.ConfigSection import ConfigSection - -class DatabaseConfig(ConfigSection): - def __init__(self, *args, **kwargs): - ConfigSection.__init__(self, *args, **kwargs) - self.db_name = None - self.dbn = None - self.config = Config(self.global_options.config_dir, False) - - @staticmethod - def get_description(): - return 'add, edit, or remove databases' - - @staticmethod - def get_commands(): - return (('add', 'configure a new database'), - ('edit', 'configure an existing database')) - - def prepare_add(self, parser, db_name, dbn): - self.db_name = db_name - self.dbn = dbn - if self.config.has_database(self.db_name): - parser.error('database already exists') - - def start_add(self): - self.config.add_database(self.db_name, self.dbn) - print 'Database added.' - - def prepare_edit(self, parser, db_name, dbn): - self.db_name = db_name - self.dbn = dbn - if not self.config.has_database(self.db_name): - parser.error('database not found') - - def start_edit(self): - if self.config.add_database(self.db_name, self.dbn): - print 'Database configured.' - else: - print 'No changes were made.' diff --git a/src/Exscriptd/config/QueueConfig.py b/src/Exscriptd/config/QueueConfig.py deleted file mode 100644 index ee6130e4..00000000 --- a/src/Exscriptd/config/QueueConfig.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright (C) 2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -from Exscriptd.Config import Config -from Exscriptd.config.ConfigSection import ConfigSection - -class QueueConfig(ConfigSection): - def __init__(self, *args, **kwargs): - ConfigSection.__init__(self, *args, **kwargs) - self.queue_name = None - self.config = Config(self.global_options.config_dir, False) - - @staticmethod - def get_description(): - return 'add, edit, or remove queues' - - @staticmethod - def get_commands(): - return (('add', 'create a new queue'), - ('edit', 'edit an existing queue')) - - def getopt_add(self, parser): - parser.add_option('--account-pool', - dest = 'account_pool', - metavar = 'STRING', - help = 'the account pool that is used') - parser.add_option('--max-threads', - dest = 'max_threads', - metavar = 'INT', - default = 5, - help = 'the name of the new queue') - - def prepare_add(self, parser, queue_name): - self.queue_name = queue_name - if self.config.has_queue(self.queue_name): - parser.error('queue already exists') - - def start_add(self): - self.config.add_queue(self.queue_name, - self.options.account_pool, - self.options.max_threads) - print 'Queue added.' - - def getopt_edit(self, parser): - self.getopt_add(parser) - - def prepare_edit(self, parser, queue_name): - self.queue_name = queue_name - if not self.config.has_queue(self.queue_name): - parser.error('queue not found') - - def start_edit(self): - if self.config.add_queue(self.queue_name, - self.options.account_pool, - self.options.max_threads): - print 'Queue configured.' - else: - print 'No changes were made.' diff --git a/src/Exscriptd/config/ServiceConfig.py b/src/Exscriptd/config/ServiceConfig.py deleted file mode 100644 index 3e310dce..00000000 --- a/src/Exscriptd/config/ServiceConfig.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright (C) 2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import sys -from Exscriptd.util import find_module_recursive -from Exscriptd.Config import Config -from Exscriptd.config.ConfigSection import ConfigSection - -class ServiceConfig(ConfigSection): - def __init__(self, *args, **kwargs): - ConfigSection.__init__(self, *args, **kwargs) - self.service_name = None - self.module_name = None - self.varname = None - self.value = None - self.config = Config(self.global_options.config_dir, False) - - @staticmethod - def get_description(): - return 'add or configure services' - - @staticmethod - def get_commands(): - return (('add', 'configure a new service'), - ('edit', 'configure an existing service'), - ('set', 'define a service variable'), - ('unset', 'remove a service variable')) - - def _assert_module_exists(self, parser, module_name): - try: - file, module_path, desc = find_module_recursive(module_name) - except ImportError: - args = repr(module_name), sys.path - msg = 'service %s not found. sys.path is %s' % args - parser.error(msg) - - def getopt_add(self, parser): - parser.add_option('--daemon', - dest = 'daemon', - metavar = 'STRING', - help = 'the daemon that is used') - parser.add_option('--queue', - dest = 'queue', - metavar = 'STRING', - help = 'the queue that is used') - - def prepare_add(self, parser, service_name, module_name): - self.service_name = service_name - self.module_name = module_name - self._assert_module_exists(parser, module_name) - if self.config.has_service(self.service_name): - parser.error('service already exists') - - def start_add(self): - self.config.add_service(self.service_name, - self.module_name, - self.options.daemon, - self.options.queue) - print 'Service added.' - - def getopt_edit(self, parser): - self.getopt_add(parser) - - def prepare_edit(self, parser, service_name): - self.service_name = service_name - if not self.config.has_service(self.service_name): - parser.error('service not found') - - def start_edit(self): - if self.config.add_service(self.service_name, - None, - self.options.daemon, - self.options.queue): - print 'Service configured.' - else: - print 'No changes were made.' - - def prepare_set(self, parser, service_name, varname, value): - self.service_name = service_name - self.varname = varname - self.value = value - - def start_set(self): - self.config.set_service_variable(self.service_name, - self.varname, - self.value) - print 'Variable set.' - - def prepare_unset(self, parser, service_name, varname): - self.service_name = service_name - self.varname = varname - - def start_unset(self): - self.config.unset_service_variable(self.service_name, self.varname) - print 'Variable removed.' diff --git a/src/Exscriptd/config/__init__.py b/src/Exscriptd/config/__init__.py deleted file mode 100644 index 981518ea..00000000 --- a/src/Exscriptd/config/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -from Exscriptd.config.AccountPoolConfig import AccountPoolConfig -from Exscriptd.config.BaseConfig import BaseConfig -from Exscriptd.config.DaemonConfig import DaemonConfig -from Exscriptd.config.DatabaseConfig import DatabaseConfig -from Exscriptd.config.ServiceConfig import ServiceConfig -from Exscriptd.config.QueueConfig import QueueConfig - -modules = { - 'account-pool': AccountPoolConfig, - 'base': BaseConfig, - 'daemon': DaemonConfig, - 'db': DatabaseConfig, - 'service': ServiceConfig, - 'queue': QueueConfig -} diff --git a/src/Exscriptd/config/exscriptd.in b/src/Exscriptd/config/exscriptd.in deleted file mode 100755 index d1876b13..00000000 --- a/src/Exscriptd/config/exscriptd.in +++ /dev/null @@ -1,155 +0,0 @@ -#! /bin/sh -### BEGIN INIT INFO -# Provides: exscriptd -# Required-Start: $remote_fs $network -# Required-Stop: $remote_fs $network -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Start and stop the Exscript server daemon -# Description: The Exscript daemon provides services for contacting -# and provisioning network devices. -### END INIT INFO - -# Author: Samuel Abels - -# Do NOT "set -e" - -# PATH should only include /usr/* if it runs after the mountnfs.sh script -PATH=/sbin:/usr/sbin:/bin:/usr/bin -DESC="Exscript daemon" -NAME=exscriptd -CONFIG_DIR=@CFG_DIR@ -DAEMON=@SCRIPT_DIR@/$NAME -PIDFILE=@PIDFILE@ -DAEMON_ARGS="--config-dir $CONFIG_DIR --pidfile $PIDFILE" -SCRIPTNAME=@INIT_DIR@/$NAME -RUN_AS_USER=exscriptd:exscriptd -export PYTHONPATH=@PYTHONPATH@ - -# Exit if the package is not installed -[ -x "$DAEMON" ] || exit 0 - -# Read configuration variable file if it is present -[ -r /etc/default/$NAME ] && . /etc/default/$NAME - -# Load the VERBOSE setting and other rcS variables -. /lib/init/vars.sh - -# Define LSB log_* functions. -# Depend on lsb-base (>= 3.0-6) to ensure that this file is present. -. /lib/lsb/init-functions - -# -# Function that starts the daemon/service -# -do_start() -{ - # Return - # 0 if daemon has been started - # 1 if daemon was already running - # 2 if daemon could not be started - start-stop-daemon -Sq --chuid $RUN_AS_USER --pidfile $PIDFILE --exec $DAEMON --name $NAME --test > /dev/null \ - || return 1 - start-stop-daemon -Sq --chuid $RUN_AS_USER --pidfile $PIDFILE --exec $DAEMON --name $NAME -- \ - $DAEMON_ARGS start \ - || return 2 - # Add code here, if necessary, that waits for the process to be ready - # to handle requests from services started subsequently which depend - # on this one. As a last resort, sleep for some time. -} - -# -# Function that stops the daemon/service -# -do_stop() -{ - # Return - # 0 if daemon has been stopped - # 1 if daemon was already stopped - # 2 if daemon could not be stopped - # other if a failure occurred - start-stop-daemon --stop --quiet --pidfile $PIDFILE - RETVAL="$?" - [ "$RETVAL" = 2 ] && return 2 - # Wait for children to finish too if this is a daemon that forks - # and if the daemon is only ever run from this initscript. - # If the above conditions are not satisfied then add some other code - # that waits for the process to drop all resources that could be - # needed by services started subsequently. A last resort is to - # sleep for some time. - start-stop-daemon --stop --quiet --oknodo --exec $DAEMON - [ "$?" = 2 ] && return 2 - # Many daemons don't delete their pidfiles when they exit. - rm -f $PIDFILE - return "$RETVAL" -} - -# -# Function that sends a SIGHUP to the daemon/service -# -do_reload() { - # - # If the daemon can reload its configuration without - # restarting (for example, when it is sent a SIGHUP), - # then implement that here. - # - start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME - return 0 -} - -case "$1" in - start) - [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" - do_start - case "$?" in - 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; - 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; - esac - ;; - stop) - [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" - do_stop - case "$?" in - 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; - 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; - esac - ;; - #reload|force-reload) - # - # If do_reload() is not implemented then leave this commented out - # and leave 'force-reload' as an alias for 'restart'. - # - #log_daemon_msg "Reloading $DESC" "$NAME" - #do_reload - #log_end_msg $? - #;; - restart|force-reload) - # - # If the "reload" option is implemented then remove the - # 'force-reload' alias - # - log_daemon_msg "Restarting $DESC" "$NAME" - do_stop - case "$?" in - 0|1) - do_start - case "$?" in - 0) log_end_msg 0 ;; - 1) log_end_msg 1 ;; # Old process is still running - *) log_end_msg 1 ;; # Failed to start - esac - ;; - *) - # Failed to stop - log_end_msg 1 - ;; - esac - ;; - *) - #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 - echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2 - exit 3 - ;; -esac - -: diff --git a/src/Exscriptd/config/main.xml.in b/src/Exscriptd/config/main.xml.in deleted file mode 100644 index 76d71f62..00000000 --- a/src/Exscriptd/config/main.xml.in +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - @LOG_DIR@ - default - http-daemon - - - - - - - - - - - ZXhzY3JpcHQtaHR0cA== - - - - - - sqlite:///@SPOOL_DIR@/db.sqlite - - - - - 5 - default - - - - - -
- 8123 - api-accounts -
-
diff --git a/src/Exscriptd/util.py b/src/Exscriptd/util.py deleted file mode 100644 index 37121068..00000000 --- a/src/Exscriptd/util.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import re -import imp - -def resolve_variables(variables, string): - def variable_sub_cb(match): - field = match.group(0) - escape = match.group(1) - varname = match.group(2) - value = variables.get(varname) - - # Check the variable name syntax. - if escape: - return '$' + varname - elif varname == '': - return '$' - - # Check the variable value. - if value is None: - msg = 'Undefined variable %s' % repr(varname) - raise Exception(msg) - return str(value) - - string_re = re.compile(r'(\\?)\$([\w_]*)') - return string_re.sub(variable_sub_cb, string) - -def find_module_recursive(name, path = None): - if not '.' in name: - return imp.find_module(name, path) - parent, children = name.split('.', 1) - module = imp.find_module(parent, path) - path = module[1] - return find_module_recursive(children, [path]) - -def synchronized(func): - """ - Decorator for synchronizing method access. - """ - def wrapped(self, *args, **kwargs): - try: - rlock = self._sync_lock - except AttributeError: - from threading import RLock - rlock = self.__dict__.setdefault('_sync_lock', RLock()) - with rlock: - return func(self, *args, **kwargs) - - wrapped.__name__ = func.__name__ - wrapped.__dict__ = func.__dict__ - wrapped.__doc__ = func.__doc__ - return wrapped diff --git a/src/Exscriptd/xml.py b/src/Exscriptd/xml.py deleted file mode 100644 index cb52a6ce..00000000 --- a/src/Exscriptd/xml.py +++ /dev/null @@ -1,377 +0,0 @@ -# Copyright (C) 2007-2010 Samuel Abels. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2, as -# published by the Free Software Foundation. -# -# This program 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. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" -Utilities for serializing/deserializing XML. -""" -from lxml import etree -import base64 -import Exscript -from Exscript import PrivateKey - -def get_list_from_etree(node): - """ - Given a node, this function looks for child elements - and returns a list of strings:: - - - foo - bar - - - @type node: lxml.etree.ElementNode - @param node: A node containing list-item elements. - @rtype: list(str) - @return: A list of strings. - """ - items = node.iterfind('list-item') - if items is None: - return [] - return [i.text.strip() for i in items if i.text is not None] - -def add_list_to_etree(root, tag, thelist, name = None): - """ - Given a list, this function creates the syntax shown in - get_list_from_etree() and adds it to the given node. - Returns the new list element. - - @type root: lxml.etree.ElementNode - @param root: The node under which the new element is added. - @type tag: str - @param tag: The tag name of the new node. - @type thelist: list(str) - @param thelist: A list of strings. - @type name: str - @param name: The name attribute of the new node. - @rtype: lxml.etree.ElementNode - @return: The new node. - """ - if name: - list_elem = etree.SubElement(root, tag, name = name) - else: - list_elem = etree.SubElement(root, tag) - for value in thelist: - item = etree.SubElement(list_elem, 'list-item') - item.text = value - return list_elem - -def get_dict_from_etree(node): - """ - Given a parent node, this function looks for child elements - and returns a dictionary of arguments:: - - - myvalue - - foo - bar - - - - @type node: lxml.etree.ElementNode - @param node: A node containing variable elements. - @rtype: dict - @return: A map of variables. - """ - if node is None: - return {} - args = {} - for child in node: - name = child.get('name').strip() - if child.tag == 'variable': - args[name] = child.text.strip() - elif child.tag == 'list': - args[name] = get_list_from_etree(child) - elif child.tag == 'map': - args[name] = get_dict_from_etree(child) - else: - raise Exception('Invalid XML tag: %s' % child.tag) - return args - -def add_dict_to_etree(root, tag, thedict, name = None): - """ - Given a dictionary, this function creates the syntax shown in - get_dict_from_etree() and adds it to the given node. - Returns the new dictionary element. - - @type root: lxml.etree.ElementNode - @param root: The node under which the new element is added. - @type tag: str - @param tag: The tag name of the new node. - @type thedict: dict(str|list) - @param thedict: A dictionary containing strings or lists. - @type name: str - @param name: The name attribute of the new node. - @rtype: lxml.etree.ElementNode - @return: The new node. - """ - if name: - arg_elem = etree.SubElement(root, tag, name = name) - else: - arg_elem = etree.SubElement(root, tag) - for name, value in thedict.iteritems(): - if isinstance(value, list): - add_list_to_etree(arg_elem, 'list', value, name = name) - elif isinstance(value, dict): - add_dict_to_etree(arg_elem, 'map', value, name = name) - elif isinstance(value, str) or isinstance(value, unicode): - variable = etree.SubElement(arg_elem, 'variable', name = name) - variable.text = value - else: - raise ValueError('unknown variable type: ' + repr(value)) - return arg_elem - -def get_host_from_etree(node): - """ - Given a node, this function returns a Exscript.Host instance. - The following XML syntax is expected, whereby the - element is optional:: - - - telnet - 23 - - ... - - - - foo - bar - - - - - The arguments are parsed using get_dict_from_etree() and attached - to the host using Exscript.Host.set_all(). - - @type node: lxml.etree.ElementNode - @param node: A element. - @rtype: Exscript.Host - @return: The resulting host. - """ - name = node.get('name', '').strip() - address = node.get('address', name).strip() - protocol = node.findtext('protocol') - tcp_port = node.findtext('tcp-port') - arg_elem = node.find('argument-list') - acc_elem = node.find('account') - args = get_dict_from_etree(arg_elem) - host = Exscript.Host(address) - if not address: - raise TypeError('host element without name or address') - if name: - host.set_name(name) - if protocol: - host.set_protocol(protocol) - if tcp_port: - host.set_tcp_port(int(tcp_port)) - if acc_elem is not None: - account = get_account_from_etree(acc_elem) - host.set_account(account) - host.set_all(args) - return host - -def add_host_to_etree(root, tag, host): - """ - Given a dictionary, this function creates the syntax shown in - get_host_from_etree() and adds it to the given node. - Returns the new host element. - - @type root: lxml.etree.ElementNode - @param root: The node under which the new element is added. - @type tag: str - @param tag: The tag name of the new node. - @type host: Exscript.Host - @param host: The host that is added. - @rtype: lxml.etree.ElementNode - @return: The new node. - """ - elem = etree.SubElement(root, - tag, - address = host.get_address(), - name = host.get_name()) - if host.get_protocol() is not None: - etree.SubElement(elem, 'protocol').text = host.get_protocol() - if host.get_tcp_port() is not None: - etree.SubElement(elem, 'tcp-port').text = str(host.get_tcp_port()) - account = host.get_account() - if account: - add_account_to_etree(elem, 'account', account) - if host.get_all(): - add_dict_to_etree(elem, 'argument-list', host.get_all()) - return elem - -def get_hosts_from_etree(node): - """ - Given an lxml.etree node, this function looks for tags and - returns a list of Exscript.Host instances. The following XML syntax - is expected, whereby the element is optional:: - - - - - - - foo - bar - - - - - - The arguments are parsed using get_arguments_from_etree() and attached - to the host using Exscript.Host.set(). - - @type node: lxml.etree.ElementNode - @param node: A node containing elements. - @rtype: list(Exscript.Host) - @return: A list of hosts. - """ - hosts = [] - for host_elem in node.iterfind('host'): - host = get_host_from_etree(host_elem) - hosts.append(host) - return hosts - -def add_hosts_to_etree(root, hosts): - """ - Given a list of hosts, this function creates the syntax shown in - get_hosts_from_etree() and adds it to the given node. - - @type root: lxml.etree.ElementNode - @param root: The node under which the new elements are added. - @type hosts: list(Exscript.Host) - @param hosts: A list of hosts. - """ - for host in hosts: - add_host_to_etree(root, 'host', host) - -def _get_password_from_node(node): - if node is None: - return None - thetype = node.get('type', 'cleartext') - password = node.text - if password is None: - return None - if thetype == 'base64': - return base64.decodestring(password) - elif thetype == 'cleartext': - return password - else: - raise ValueError('invalid password type: ' + thetype) - -def _add_password_node(parent, password, tag = 'password'): - node = etree.SubElement(parent, tag, type = 'base64') - if password is not None: - node.text = base64.encodestring(password).strip() - return node - -def get_account_from_etree(node): - """ - Given a node, this function returns a Exscript.Account instance. - The following XML syntax is expected, whereby the children of - are all optional:: - - - Zm9v - bar - /path/to/my/ssh/key - - - The and tags have an optional type - attribute defaulting to 'cleartext'. Allowed values are 'cleartext' - and 'base64'. - - @type node: lxml.etree.ElementNode - @param node: A element. - @rtype: Exscript.Account - @return: The resulting account. - """ - name = node.get('name', '').strip() - password1_elem = node.find('password') - password2_elem = node.find('authorization-password') - keyfile = node.findtext('keyfile') - if keyfile is None: - key = None - else: - key = PrivateKey.from_file(keyfile) - account = Exscript.Account(name, key = key) - account.set_password(_get_password_from_node(password1_elem)) - account.set_authorization_password(_get_password_from_node(password2_elem)) - return account - -def add_account_to_etree(root, tag, account): - """ - Given an account object, this function creates the syntax shown in - get_host_from_etree() and adds it to the given node. - Returns the new host element. - - @type root: lxml.etree.ElementNode - @param root: The node under which the new element is added. - @type tag: str - @param tag: The tag name of the new node. - @type account: Exscript.Account - @param account: The account that is added. - @rtype: lxml.etree.ElementNode - @return: The new node. - """ - elem = etree.SubElement(root, tag, name = account.get_name()) - _add_password_node(elem, account.get_password()) - _add_password_node(elem, - account.get_authorization_password(), - tag = 'authorization-password') - key = account.get_key() - if key is not None: - etree.SubElement(elem, 'keyfile').text = key.get_filename() - return elem - -def get_accounts_from_etree(node): - """ - Given an lxml.etree node, this function looks for tags and - returns a list of Exscript.Account instances. The following XML syntax - is expected:: - - - - - ... - - ... - - - The individual accounts are parsed using L{get_account_from_etree()}. - - @type node: lxml.etree.ElementNode - @param node: A node containing elements. - @rtype: list(Exscript.Account) - @return: A list of accounts. - """ - accounts = [] - for account_elem in node.iterfind('account'): - account = get_account_from_etree(account_elem) - accounts.append(account) - return accounts - -def add_accounts_to_etree(root, accounts): - """ - Given a list of accounts, this function creates the syntax shown in - get_accounts_from_etree() and adds it to the given node. - - @type root: lxml.etree.ElementNode - @param root: The node under which the new elements are added. - @type accounts: list(Exscript.Account) - @param accounts: A list of accounts. - """ - for account in accounts: - add_account_to_etree(root, 'account', account) diff --git a/tests/Exscriptd/OrderDBTest.py b/tests/Exscriptd/OrderDBTest.py deleted file mode 100644 index 01375720..00000000 --- a/tests/Exscriptd/OrderDBTest.py +++ /dev/null @@ -1,262 +0,0 @@ -import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) - -from tempfile import NamedTemporaryFile -from getpass import getuser -from sqlalchemy import create_engine -from Exscriptd.Order import Order -from Exscriptd.Task import Task -from Exscriptd.OrderDB import OrderDB - -def testfunc(foo): - pass - -class OrderDBTest(unittest.TestCase): - CORRELATE = OrderDB - - def setUp(self): - from sqlalchemy.pool import NullPool - self.dbfile = NamedTemporaryFile() - self.engine = create_engine('sqlite:///' + self.dbfile.name, - poolclass = NullPool) - self.db = OrderDB(self.engine) - - def tearDown(self): - self.dbfile.close() - - def testConstructor(self): - db = OrderDB(self.engine) - - def testInstall(self): - self.db.install() - - def testUninstall(self): - self.testInstall() - self.db.uninstall() - self.db.install() - - def testClearDatabase(self): - self.testAddOrder() - self.db.clear_database() - - orders = self.db.get_orders() - self.assert_(len(orders) == 0) - - def testDebug(self): - self.assert_(not self.engine.echo) - self.db.debug() - self.assert_(self.engine.echo) - self.db.debug(False) - self.assert_(not self.engine.echo) - - def testSetTablePrefix(self): - self.assertEqual(self.db.get_table_prefix(), 'exscriptd_') - self.db.set_table_prefix('foo') - self.assertEqual(self.db.get_table_prefix(), 'foo') - self.db.install() - self.db.uninstall() - - def testGetTablePrefix(self): - self.testSetTablePrefix() - - def testAddOrder(self): - self.testInstall() - - order1 = Order('fooservice') - self.assertEqual(order1.get_created_by(), getuser()) - self.assertEqual(order1.get_description(), '') - self.assertEqual(order1.get_progress(), .0) - order1.created_by = 'this test' - order1.set_description('my description') - self.assertEqual(order1.get_created_by(), 'this test') - self.assertEqual(order1.get_description(), 'my description') - - # Save the order. - self.assert_(order1.get_id() is None) - self.db.add_order(order1) - order_id = order1.get_id() - self.assert_(order_id is not None) - - def assert_progress(value): - progress = self.db.get_order_progress_from_id(order_id) - theorder = self.db.get_order(id = order_id) - self.assertEqual(progress, value) - self.assertEqual(theorder.get_progress(), value) - - # Check that the order is stored. - order = self.db.get_order(id = order_id) - self.assertEqual(order.get_id(), order_id) - self.assertEqual(order.get_created_by(), 'this test') - self.assertEqual(order.get_closed_timestamp(), None) - self.assertEqual(order.get_description(), 'my description') - assert_progress(.0) - - # Check that an order that has no tasks show progress 100% when - # it is closed. - order.close() - self.db.save_order(order) - assert_progress(1.0) - - # Add some sub-tasks. - task1 = Task(order.id, 'my test task') - self.db.save_task(task1) - assert_progress(.0) - - task2 = Task(order.id, 'another test task') - self.db.save_task(task2) - assert_progress(.0) - - # Change the progress, re-check. - task1.set_progress(.5) - self.db.save_task(task1) - assert_progress(.25) - - task2.set_progress(.5) - self.db.save_task(task2) - assert_progress(.5) - - task1.set_progress(1.0) - self.db.save_task(task1) - assert_progress(.75) - - task2.set_progress(1.0) - self.db.save_task(task2) - assert_progress(1.0) - - def testSaveOrder(self): - self.testInstall() - - order1 = Order('fooservice') - - self.assert_(order1.get_id() is None) - self.db.save_order(order1) - - # Check that the order is stored. - order2 = self.db.get_order(id = order1.get_id()) - self.assertEqual(order1.get_id(), order2.get_id()) - - def testGetOrderProgressFromId(self): - self.testInstall() - - order = Order('fooservice') - self.db.save_order(order) - id = order.get_id() - self.assertEqual(self.db.get_order_progress_from_id(id), .0) - - order.close() - self.db.save_order(order) - self.assertEqual(self.db.get_order_progress_from_id(id), 1.0) - - task1 = Task(order.id, 'my test task') - self.db.save_task(task1) - self.assertEqual(self.db.get_order_progress_from_id(id), .0) - - task2 = Task(order.id, 'another test task') - self.db.save_task(task2) - self.assertEqual(self.db.get_order_progress_from_id(id), .0) - - task1.set_progress(.5) - self.db.save_task(task1) - self.assertEqual(self.db.get_order_progress_from_id(id), .25) - task2.set_progress(.5) - self.db.save_task(task2) - self.assertEqual(self.db.get_order_progress_from_id(id), .5) - task1.set_progress(1.0) - self.db.save_task(task1) - self.assertEqual(self.db.get_order_progress_from_id(id), .75) - task2.set_progress(1.0) - self.db.save_task(task2) - self.assertEqual(self.db.get_order_progress_from_id(id), 1.0) - - def testGetOrder(self): - self.testAddOrder() - - def testCountOrders(self): - self.testInstall() - self.assertEqual(self.db.count_orders(id = 1), 0) - self.assertEqual(self.db.count_orders(), 0) - self.testAddOrder() - self.assertEqual(self.db.count_orders(), 1) - self.testAddOrder() - self.assertEqual(self.db.count_orders(), 2) - self.assertEqual(self.db.count_orders(id = 1), 1) - - def testGetOrders(self): - self.testAddOrder() - self.testAddOrder() - self.assertEqual(self.db.count_orders(), 2) - orders = self.db.get_orders() - self.assertEqual(len(orders), 2) - - def testCloseOpenOrders(self): - self.testInstall() - - order = Order('fooservice') - self.db.add_order(order) - order = self.db.get_orders()[0] - self.assertEqual(order.closed, None) - - self.db.close_open_orders() - order = self.db.get_orders()[0] - self.failIfEqual(order.get_closed_timestamp(), None) - - def testSaveTask(self): - self.testInstall() - - order = Order('fooservice') - self.db.save_order(order) - - task = Task(order.id, 'my test task') - self.assert_(task.id is None) - self.db.save_task(task) - self.assert_(task.id is not None) - - def testGetTask(self): - self.testInstall() - - order = Order('fooservice') - self.db.save_order(order) - - task1 = Task(order.id, 'my test task') - self.db.save_task(task1) - loaded_task = self.db.get_task() - self.assertEqual(task1.id, loaded_task.id) - - task2 = Task(order.id, 'another test task') - self.db.save_task(task2) - self.assertRaises(IndexError, self.db.get_task) - - def testGetTasks(self): - self.testInstall() - - order = Order('fooservice') - self.db.save_order(order) - - task1 = Task(order.id, 'my test task') - task2 = Task(order.id, 'another test task') - self.db.save_task(task1) - self.db.save_task(task2) - - id_list1 = sorted([task1.id, task2.id]) - id_list2 = sorted([task.id for task in self.db.get_tasks()]) - self.assertEqual(id_list1, id_list2) - - tasks = self.db.get_tasks(order_id = order.id) - id_list2 = sorted([task.id for task in tasks]) - self.assertEqual(id_list1, id_list2) - - id_list2 = [task.id for task in self.db.get_tasks(order_id = 2)] - self.assertEqual([], id_list2) - - def testCountTasks(self): - self.testInstall() - self.assertEqual(self.db.count_tasks(), 0) - self.testSaveTask() - self.assertEqual(self.db.count_tasks(), 1) - self.testSaveTask() - self.assertEqual(self.db.count_tasks(), 2) - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(OrderDBTest) -if __name__ == '__main__': - unittest.TextTestRunner(verbosity = 2).run(suite()) diff --git a/tests/Exscriptd/pythonservice/order.xml b/tests/Exscriptd/pythonservice/order.xml deleted file mode 100644 index 0e589823..00000000 --- a/tests/Exscriptd/pythonservice/order.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/tests/Exscriptd/pythonservice/test.py b/tests/Exscriptd/pythonservice/test.py deleted file mode 100644 index be6ea79b..00000000 --- a/tests/Exscriptd/pythonservice/test.py +++ /dev/null @@ -1,20 +0,0 @@ -print "Hello Python-Service!" - -def run(conn, order): - """ - Called whenever a host that is associated with an order was contacted. - """ - hostname = conn.get_host().get_name() - print "Hello from python-service run()!", __service__.name, order.id, hostname - -def enter(order): - """ - Called whenever a new order was received. - If this funtion returns True the order is accepted. Otherwise, - the order is rejected. - """ - print "Hello from python-service enter()!", __service__.name, order.id - callback = bind(run, order) - __service__.enqueue_hosts(order, order.get_hosts(), callback) - __service__.set_order_status(order, 'queued') - return True diff --git a/tests/Exscriptd/run_suite.py b/tests/Exscriptd/run_suite.py deleted file mode 120000 index 7abb01ab..00000000 --- a/tests/Exscriptd/run_suite.py +++ /dev/null @@ -1 +0,0 @@ -../Exscript/run_suite.py \ No newline at end of file From e9b7c908851a31fae49dfb782eb73313ab864632 Mon Sep 17 00:00:00 2001 From: Samuel Abels Date: Sun, 5 Mar 2017 13:18:47 +0100 Subject: [PATCH 3/5] update README --- README.md | 62 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 52e4f1e4..d2e74860 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ -Exscript -======== +# Exscript + +## Summary Exscript is a Python module and a template processor for automating network connections over protocols such as Telnet or SSH. We attempt to create the best possible set of tools for working with Telnet and SSH. -Exscript is also an excellent and much more powerful Net::Telnet replacement, -so we welcome all you Perl developers! +Exscript also provides a set of tools and functions for sysadmins, that +simplify **regular expression matching**, **reporting** by email, **logging**, +or **syslog** handling, **CSV parsing**, **ip address handling**, and many more. Exscript may be used to automate sessions with routers from Cisco, Juniper, OneAccess, Huawei, or any others. If you want to configures machines @@ -18,22 +20,54 @@ The Exscript template language is also in some ways comparable to Expect, but has some unique features that make it a lot easier to use and understand for non-developers. +## Method 1: Using Exscript with Python -Links -===== +```python +from Exscript.util.start import start +from Exscript.util.file import get_hosts_from_file +from Exscript.util.file import get_accounts_from_file -Mailing List: http://groups.google.com/group/exscript/ +def do_something(job, host, conn): + conn.execute('uname -a') +accounts = get_accounts_from_file('accounts.cfg') +hosts = get_hosts_from_file('myhosts.txt') +start(accounts, hosts, do_something, max_threads=2) +``` -Dependencies -============ +Check out the Python tutorial: -* Python >=2.6 , <=2.7 (no Python 3 yet) -* Python-crypto -* Paramiko +https://github.com/knipknap/exscript/wiki/Python-API-Tutorial + +## Method 2: Using the Exscript command line tool + +Create a file named `test.exscript` with the following content: + +``` +uname -a +``` + +To run this Exscript template, just start Exscript using the following command: + +``` +exscript test.exscript ssh://localhost +``` +Awesome fact: Just replace `ssh://` by `telnet://` and it should still work with Telnet devices. -Installation -============ + +## Documentation + +Full documentation is here: + +https://github.com/knipknap/exscript/wiki + +## Installation Simply follow the [installation guide](https://github.com/knipknap/exscript/wiki/Installation-Guide "Installation Guide"). + +## Dependencies + +* Python >=2.6 , <=2.7 (we are working on Python 3 support) +* Python-crypto +* Paramiko From 29c2374fe9b3914c462e7dc607bc67590831f628 Mon Sep 17 00:00:00 2001 From: Samuel Abels Date: Sun, 5 Mar 2017 13:24:28 +0100 Subject: [PATCH 4/5] remove src/ directory for a more standard Python module layout --- {src/Exscript => Exscript}/Account.py | 0 {src/Exscript => Exscript}/AccountManager.py | 0 {src/Exscript => Exscript}/AccountPool.py | 0 {src/Exscript => Exscript}/AccountProxy.py | 0 {src/Exscript => Exscript}/FileLogger.py | 0 {src/Exscript => Exscript}/Host.py | 0 {src/Exscript => Exscript}/Log.py | 0 {src/Exscript => Exscript}/Logfile.py | 0 {src/Exscript => Exscript}/Logger.py | 0 {src/Exscript => Exscript}/LoggerProxy.py | 0 {src/Exscript => Exscript}/PrivateKey.py | 0 {src/Exscript => Exscript}/Queue.py | 0 {src/Exscript => Exscript}/__init__.py | 0 .../emulators/CommandSet.py | 0 .../emulators/IOSEmulator.py | 0 .../emulators/VirtualDevice.py | 0 .../emulators/__init__.py | 0 .../external/__init__.py | 0 .../external/otp/AppendixB.py | 0 .../external/otp/__init__.py | 0 .../external/otp/keywrangling.py | 0 .../Exscript => Exscript}/external/otp/otp.py | 0 .../interpreter/Append.py | 0 .../interpreter/Assign.py | 0 .../Exscript => Exscript}/interpreter/Code.py | 0 .../interpreter/Enter.py | 0 .../interpreter/Exception.py | 0 .../interpreter/Execute.py | 0 .../interpreter/Expression.py | 0 .../interpreter/ExpressionNode.py | 0 .../interpreter/Extract.py | 0 .../Exscript => Exscript}/interpreter/Fail.py | 0 .../interpreter/FunctionCall.py | 0 .../interpreter/IfCondition.py | 0 .../Exscript => Exscript}/interpreter/Loop.py | 0 .../interpreter/Number.py | 0 .../interpreter/Parser.py | 0 .../interpreter/Program.py | 0 .../interpreter/Regex.py | 0 .../interpreter/Scope.py | 0 .../interpreter/String.py | 0 .../interpreter/Template.py | 0 .../Exscript => Exscript}/interpreter/Term.py | 0 {src/Exscript => Exscript}/interpreter/Try.py | 0 .../interpreter/Variable.py | 0 .../interpreter/__init__.py | 0 .../parselib/Exception.py | 0 {src/Exscript => Exscript}/parselib/Lexer.py | 0 {src/Exscript => Exscript}/parselib/Token.py | 0 .../parselib/__init__.py | 0 {src/Exscript => Exscript}/protocols/Dummy.py | 0 .../protocols/Exception.py | 0 .../protocols/OsGuesser.py | 0 .../protocols/Protocol.py | 0 {src/Exscript => Exscript}/protocols/SSH2.py | 0 .../Exscript => Exscript}/protocols/Telnet.py | 0 .../protocols/__init__.py | 0 .../protocols/drivers/__init__.py | 0 .../protocols/drivers/ace.py | 0 .../protocols/drivers/aironet.py | 0 .../protocols/drivers/aix.py | 0 .../protocols/drivers/arbor_peakflow.py | 0 .../protocols/drivers/aruba.py | 0 .../protocols/drivers/bigip.py | 0 .../protocols/drivers/brocade.py | 0 .../protocols/drivers/driver.py | 0 .../protocols/drivers/enterasys.py | 0 .../protocols/drivers/enterasys_wc.py | 0 .../protocols/drivers/eos.py | 0 .../protocols/drivers/ericsson_ban.py | 0 .../protocols/drivers/fortios.py | 0 .../protocols/drivers/generic.py | 0 .../protocols/drivers/hp_pro_curve.py | 0 .../protocols/drivers/ios.py | 0 .../protocols/drivers/ios_xr.py | 0 .../protocols/drivers/isam.py | 0 .../protocols/drivers/junos.py | 0 .../protocols/drivers/junos_erx.py | 0 .../protocols/drivers/mrv.py | 0 .../protocols/drivers/nxos.py | 0 .../protocols/drivers/one_os.py | 0 .../protocols/drivers/rios.py | 0 .../protocols/drivers/shell.py | 0 .../protocols/drivers/smart_edge_os.py | 0 .../protocols/drivers/sros.py | 0 .../protocols/drivers/vrp.py | 0 .../protocols/drivers/vxworks.py | 0 .../protocols/drivers/zte.py | 0 .../protocols/telnetlib.py | 0 {src/Exscript => Exscript}/servers/HTTPd.py | 0 {src/Exscript => Exscript}/servers/SSHd.py | 0 {src/Exscript => Exscript}/servers/Server.py | 0 {src/Exscript => Exscript}/servers/Telnetd.py | 0 .../Exscript => Exscript}/servers/__init__.py | 0 {src/Exscript => Exscript}/stdlib/__init__.py | 0 .../stdlib/connection.py | 0 {src/Exscript => Exscript}/stdlib/crypt.py | 0 {src/Exscript => Exscript}/stdlib/file.py | 0 {src/Exscript => Exscript}/stdlib/ipv4.py | 0 {src/Exscript => Exscript}/stdlib/list.py | 0 {src/Exscript => Exscript}/stdlib/mysys.py | 0 {src/Exscript => Exscript}/stdlib/string.py | 0 {src/Exscript => Exscript}/stdlib/util.py | 0 {src/Exscript => Exscript}/util/__init__.py | 0 {src/Exscript => Exscript}/util/buffer.py | 0 {src/Exscript => Exscript}/util/cast.py | 0 {src/Exscript => Exscript}/util/crypt.py | 0 {src/Exscript => Exscript}/util/daemonize.py | 0 {src/Exscript => Exscript}/util/decorator.py | 0 {src/Exscript => Exscript}/util/event.py | 0 {src/Exscript => Exscript}/util/file.py | 0 {src/Exscript => Exscript}/util/impl.py | 0 {src/Exscript => Exscript}/util/interact.py | 0 {src/Exscript => Exscript}/util/ip.py | 0 {src/Exscript => Exscript}/util/ipv4.py | 0 {src/Exscript => Exscript}/util/ipv6.py | 0 {src/Exscript => Exscript}/util/log.py | 0 {src/Exscript => Exscript}/util/mail.py | 0 {src/Exscript => Exscript}/util/match.py | 0 {src/Exscript => Exscript}/util/pidutil.py | 0 {src/Exscript => Exscript}/util/report.py | 0 {src/Exscript => Exscript}/util/sigint.py | 0 .../util/sigintcatcher.py | 0 {src/Exscript => Exscript}/util/start.py | 0 {src/Exscript => Exscript}/util/syslog.py | 0 {src/Exscript => Exscript}/util/template.py | 0 {src/Exscript => Exscript}/util/tty.py | 0 {src/Exscript => Exscript}/util/url.py | 0 {src/Exscript => Exscript}/util/weakmethod.py | 0 {src/Exscript => Exscript}/version.py | 0 .../workqueue/DBPipeline.py | 0 {src/Exscript => Exscript}/workqueue/Job.py | 0 .../workqueue/MainLoop.py | 0 .../workqueue/Pipeline.py | 0 {src/Exscript => Exscript}/workqueue/Task.py | 0 .../workqueue/WorkQueue.py | 0 .../workqueue/__init__.py | 0 Makefile | 2 +- doc/Makefile | 26 ++++++++++++- doc/mkapidoc.py | 39 ------------------- setup.py | 11 ++---- tests/Exscript/AccountManagerTest.py | 2 +- tests/Exscript/AccountPoolTest.py | 2 +- tests/Exscript/AccountTest.py | 2 +- tests/Exscript/FileLoggerTest.py | 2 +- tests/Exscript/HostTest.py | 2 +- tests/Exscript/LogTest.py | 2 +- tests/Exscript/LogfileTest.py | 2 +- tests/Exscript/LoggerTest.py | 2 +- tests/Exscript/PrivateKeyTest.py | 2 +- tests/Exscript/QueueTest.py | 2 +- tests/Exscript/TemplateTest.py | 2 +- tests/Exscript/emulators/CommandSetTest.py | 2 +- tests/Exscript/emulators/IOSEmulatorTest.py | 2 +- tests/Exscript/emulators/VirtualDeviceTest.py | 2 +- tests/Exscript/protocols/DummyTest.py | 2 +- tests/Exscript/protocols/OsGuesserTest.py | 2 +- tests/Exscript/protocols/ProtocolTest.py | 2 +- tests/Exscript/protocols/SSH2Test.py | 2 +- tests/Exscript/protocols/TelnetTest.py | 2 +- tests/Exscript/servers/SSHdTest.py | 2 +- tests/Exscript/servers/ServerTest.py | 2 +- tests/Exscript/servers/TelnetdTest.py | 2 +- tests/Exscript/util/bufferTest.py | 2 +- tests/Exscript/util/castTest.py | 2 +- tests/Exscript/util/cryptTest.py | 2 +- tests/Exscript/util/decoratorTest.py | 2 +- tests/Exscript/util/eventTest.py | 2 +- tests/Exscript/util/fileTest.py | 2 +- tests/Exscript/util/interactTest.py | 2 +- tests/Exscript/util/ipTest.py | 2 +- tests/Exscript/util/ipv4Test.py | 2 +- tests/Exscript/util/ipv6Test.py | 2 +- tests/Exscript/util/mailTest.py | 2 +- tests/Exscript/util/matchTest.py | 2 +- tests/Exscript/util/reportTest.py | 2 +- tests/Exscript/util/startTest.py | 2 +- tests/Exscript/util/syslogTest.py | 2 +- tests/Exscript/util/ttyTest.py | 2 +- tests/Exscript/util/urlTest.py | 2 +- tests/Exscript/util/weakmethodTest.py | 2 +- tests/Exscript/workqueue/JobTest.py | 2 +- tests/Exscript/workqueue/MainLoopTest.py | 2 +- tests/Exscript/workqueue/PipelineTest.py | 2 +- tests/Exscript/workqueue/TaskTest.py | 2 +- tests/Exscript/workqueue/WorkQueueTest.py | 2 +- version.sh | 2 +- 187 files changed, 74 insertions(+), 96 deletions(-) rename {src/Exscript => Exscript}/Account.py (100%) rename {src/Exscript => Exscript}/AccountManager.py (100%) rename {src/Exscript => Exscript}/AccountPool.py (100%) rename {src/Exscript => Exscript}/AccountProxy.py (100%) rename {src/Exscript => Exscript}/FileLogger.py (100%) rename {src/Exscript => Exscript}/Host.py (100%) rename {src/Exscript => Exscript}/Log.py (100%) rename {src/Exscript => Exscript}/Logfile.py (100%) rename {src/Exscript => Exscript}/Logger.py (100%) rename {src/Exscript => Exscript}/LoggerProxy.py (100%) rename {src/Exscript => Exscript}/PrivateKey.py (100%) rename {src/Exscript => Exscript}/Queue.py (100%) rename {src/Exscript => Exscript}/__init__.py (100%) rename {src/Exscript => Exscript}/emulators/CommandSet.py (100%) rename {src/Exscript => Exscript}/emulators/IOSEmulator.py (100%) rename {src/Exscript => Exscript}/emulators/VirtualDevice.py (100%) rename {src/Exscript => Exscript}/emulators/__init__.py (100%) rename {src/Exscript => Exscript}/external/__init__.py (100%) rename {src/Exscript => Exscript}/external/otp/AppendixB.py (100%) rename {src/Exscript => Exscript}/external/otp/__init__.py (100%) rename {src/Exscript => Exscript}/external/otp/keywrangling.py (100%) rename {src/Exscript => Exscript}/external/otp/otp.py (100%) rename {src/Exscript => Exscript}/interpreter/Append.py (100%) rename {src/Exscript => Exscript}/interpreter/Assign.py (100%) rename {src/Exscript => Exscript}/interpreter/Code.py (100%) rename {src/Exscript => Exscript}/interpreter/Enter.py (100%) rename {src/Exscript => Exscript}/interpreter/Exception.py (100%) rename {src/Exscript => Exscript}/interpreter/Execute.py (100%) rename {src/Exscript => Exscript}/interpreter/Expression.py (100%) rename {src/Exscript => Exscript}/interpreter/ExpressionNode.py (100%) rename {src/Exscript => Exscript}/interpreter/Extract.py (100%) rename {src/Exscript => Exscript}/interpreter/Fail.py (100%) rename {src/Exscript => Exscript}/interpreter/FunctionCall.py (100%) rename {src/Exscript => Exscript}/interpreter/IfCondition.py (100%) rename {src/Exscript => Exscript}/interpreter/Loop.py (100%) rename {src/Exscript => Exscript}/interpreter/Number.py (100%) rename {src/Exscript => Exscript}/interpreter/Parser.py (100%) rename {src/Exscript => Exscript}/interpreter/Program.py (100%) rename {src/Exscript => Exscript}/interpreter/Regex.py (100%) rename {src/Exscript => Exscript}/interpreter/Scope.py (100%) rename {src/Exscript => Exscript}/interpreter/String.py (100%) rename {src/Exscript => Exscript}/interpreter/Template.py (100%) rename {src/Exscript => Exscript}/interpreter/Term.py (100%) rename {src/Exscript => Exscript}/interpreter/Try.py (100%) rename {src/Exscript => Exscript}/interpreter/Variable.py (100%) rename {src/Exscript => Exscript}/interpreter/__init__.py (100%) rename {src/Exscript => Exscript}/parselib/Exception.py (100%) rename {src/Exscript => Exscript}/parselib/Lexer.py (100%) rename {src/Exscript => Exscript}/parselib/Token.py (100%) rename {src/Exscript => Exscript}/parselib/__init__.py (100%) rename {src/Exscript => Exscript}/protocols/Dummy.py (100%) rename {src/Exscript => Exscript}/protocols/Exception.py (100%) rename {src/Exscript => Exscript}/protocols/OsGuesser.py (100%) rename {src/Exscript => Exscript}/protocols/Protocol.py (100%) rename {src/Exscript => Exscript}/protocols/SSH2.py (100%) rename {src/Exscript => Exscript}/protocols/Telnet.py (100%) rename {src/Exscript => Exscript}/protocols/__init__.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/__init__.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/ace.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/aironet.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/aix.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/arbor_peakflow.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/aruba.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/bigip.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/brocade.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/driver.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/enterasys.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/enterasys_wc.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/eos.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/ericsson_ban.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/fortios.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/generic.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/hp_pro_curve.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/ios.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/ios_xr.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/isam.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/junos.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/junos_erx.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/mrv.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/nxos.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/one_os.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/rios.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/shell.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/smart_edge_os.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/sros.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/vrp.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/vxworks.py (100%) rename {src/Exscript => Exscript}/protocols/drivers/zte.py (100%) rename {src/Exscript => Exscript}/protocols/telnetlib.py (100%) rename {src/Exscript => Exscript}/servers/HTTPd.py (100%) rename {src/Exscript => Exscript}/servers/SSHd.py (100%) rename {src/Exscript => Exscript}/servers/Server.py (100%) rename {src/Exscript => Exscript}/servers/Telnetd.py (100%) rename {src/Exscript => Exscript}/servers/__init__.py (100%) rename {src/Exscript => Exscript}/stdlib/__init__.py (100%) rename {src/Exscript => Exscript}/stdlib/connection.py (100%) rename {src/Exscript => Exscript}/stdlib/crypt.py (100%) rename {src/Exscript => Exscript}/stdlib/file.py (100%) rename {src/Exscript => Exscript}/stdlib/ipv4.py (100%) rename {src/Exscript => Exscript}/stdlib/list.py (100%) rename {src/Exscript => Exscript}/stdlib/mysys.py (100%) rename {src/Exscript => Exscript}/stdlib/string.py (100%) rename {src/Exscript => Exscript}/stdlib/util.py (100%) rename {src/Exscript => Exscript}/util/__init__.py (100%) rename {src/Exscript => Exscript}/util/buffer.py (100%) rename {src/Exscript => Exscript}/util/cast.py (100%) rename {src/Exscript => Exscript}/util/crypt.py (100%) rename {src/Exscript => Exscript}/util/daemonize.py (100%) rename {src/Exscript => Exscript}/util/decorator.py (100%) rename {src/Exscript => Exscript}/util/event.py (100%) rename {src/Exscript => Exscript}/util/file.py (100%) rename {src/Exscript => Exscript}/util/impl.py (100%) rename {src/Exscript => Exscript}/util/interact.py (100%) rename {src/Exscript => Exscript}/util/ip.py (100%) rename {src/Exscript => Exscript}/util/ipv4.py (100%) rename {src/Exscript => Exscript}/util/ipv6.py (100%) rename {src/Exscript => Exscript}/util/log.py (100%) rename {src/Exscript => Exscript}/util/mail.py (100%) rename {src/Exscript => Exscript}/util/match.py (100%) rename {src/Exscript => Exscript}/util/pidutil.py (100%) rename {src/Exscript => Exscript}/util/report.py (100%) rename {src/Exscript => Exscript}/util/sigint.py (100%) rename {src/Exscript => Exscript}/util/sigintcatcher.py (100%) rename {src/Exscript => Exscript}/util/start.py (100%) rename {src/Exscript => Exscript}/util/syslog.py (100%) rename {src/Exscript => Exscript}/util/template.py (100%) rename {src/Exscript => Exscript}/util/tty.py (100%) rename {src/Exscript => Exscript}/util/url.py (100%) rename {src/Exscript => Exscript}/util/weakmethod.py (100%) rename {src/Exscript => Exscript}/version.py (100%) rename {src/Exscript => Exscript}/workqueue/DBPipeline.py (100%) rename {src/Exscript => Exscript}/workqueue/Job.py (100%) rename {src/Exscript => Exscript}/workqueue/MainLoop.py (100%) rename {src/Exscript => Exscript}/workqueue/Pipeline.py (100%) rename {src/Exscript => Exscript}/workqueue/Task.py (100%) rename {src/Exscript => Exscript}/workqueue/WorkQueue.py (100%) rename {src/Exscript => Exscript}/workqueue/__init__.py (100%) delete mode 100644 doc/mkapidoc.py diff --git a/src/Exscript/Account.py b/Exscript/Account.py similarity index 100% rename from src/Exscript/Account.py rename to Exscript/Account.py diff --git a/src/Exscript/AccountManager.py b/Exscript/AccountManager.py similarity index 100% rename from src/Exscript/AccountManager.py rename to Exscript/AccountManager.py diff --git a/src/Exscript/AccountPool.py b/Exscript/AccountPool.py similarity index 100% rename from src/Exscript/AccountPool.py rename to Exscript/AccountPool.py diff --git a/src/Exscript/AccountProxy.py b/Exscript/AccountProxy.py similarity index 100% rename from src/Exscript/AccountProxy.py rename to Exscript/AccountProxy.py diff --git a/src/Exscript/FileLogger.py b/Exscript/FileLogger.py similarity index 100% rename from src/Exscript/FileLogger.py rename to Exscript/FileLogger.py diff --git a/src/Exscript/Host.py b/Exscript/Host.py similarity index 100% rename from src/Exscript/Host.py rename to Exscript/Host.py diff --git a/src/Exscript/Log.py b/Exscript/Log.py similarity index 100% rename from src/Exscript/Log.py rename to Exscript/Log.py diff --git a/src/Exscript/Logfile.py b/Exscript/Logfile.py similarity index 100% rename from src/Exscript/Logfile.py rename to Exscript/Logfile.py diff --git a/src/Exscript/Logger.py b/Exscript/Logger.py similarity index 100% rename from src/Exscript/Logger.py rename to Exscript/Logger.py diff --git a/src/Exscript/LoggerProxy.py b/Exscript/LoggerProxy.py similarity index 100% rename from src/Exscript/LoggerProxy.py rename to Exscript/LoggerProxy.py diff --git a/src/Exscript/PrivateKey.py b/Exscript/PrivateKey.py similarity index 100% rename from src/Exscript/PrivateKey.py rename to Exscript/PrivateKey.py diff --git a/src/Exscript/Queue.py b/Exscript/Queue.py similarity index 100% rename from src/Exscript/Queue.py rename to Exscript/Queue.py diff --git a/src/Exscript/__init__.py b/Exscript/__init__.py similarity index 100% rename from src/Exscript/__init__.py rename to Exscript/__init__.py diff --git a/src/Exscript/emulators/CommandSet.py b/Exscript/emulators/CommandSet.py similarity index 100% rename from src/Exscript/emulators/CommandSet.py rename to Exscript/emulators/CommandSet.py diff --git a/src/Exscript/emulators/IOSEmulator.py b/Exscript/emulators/IOSEmulator.py similarity index 100% rename from src/Exscript/emulators/IOSEmulator.py rename to Exscript/emulators/IOSEmulator.py diff --git a/src/Exscript/emulators/VirtualDevice.py b/Exscript/emulators/VirtualDevice.py similarity index 100% rename from src/Exscript/emulators/VirtualDevice.py rename to Exscript/emulators/VirtualDevice.py diff --git a/src/Exscript/emulators/__init__.py b/Exscript/emulators/__init__.py similarity index 100% rename from src/Exscript/emulators/__init__.py rename to Exscript/emulators/__init__.py diff --git a/src/Exscript/external/__init__.py b/Exscript/external/__init__.py similarity index 100% rename from src/Exscript/external/__init__.py rename to Exscript/external/__init__.py diff --git a/src/Exscript/external/otp/AppendixB.py b/Exscript/external/otp/AppendixB.py similarity index 100% rename from src/Exscript/external/otp/AppendixB.py rename to Exscript/external/otp/AppendixB.py diff --git a/src/Exscript/external/otp/__init__.py b/Exscript/external/otp/__init__.py similarity index 100% rename from src/Exscript/external/otp/__init__.py rename to Exscript/external/otp/__init__.py diff --git a/src/Exscript/external/otp/keywrangling.py b/Exscript/external/otp/keywrangling.py similarity index 100% rename from src/Exscript/external/otp/keywrangling.py rename to Exscript/external/otp/keywrangling.py diff --git a/src/Exscript/external/otp/otp.py b/Exscript/external/otp/otp.py similarity index 100% rename from src/Exscript/external/otp/otp.py rename to Exscript/external/otp/otp.py diff --git a/src/Exscript/interpreter/Append.py b/Exscript/interpreter/Append.py similarity index 100% rename from src/Exscript/interpreter/Append.py rename to Exscript/interpreter/Append.py diff --git a/src/Exscript/interpreter/Assign.py b/Exscript/interpreter/Assign.py similarity index 100% rename from src/Exscript/interpreter/Assign.py rename to Exscript/interpreter/Assign.py diff --git a/src/Exscript/interpreter/Code.py b/Exscript/interpreter/Code.py similarity index 100% rename from src/Exscript/interpreter/Code.py rename to Exscript/interpreter/Code.py diff --git a/src/Exscript/interpreter/Enter.py b/Exscript/interpreter/Enter.py similarity index 100% rename from src/Exscript/interpreter/Enter.py rename to Exscript/interpreter/Enter.py diff --git a/src/Exscript/interpreter/Exception.py b/Exscript/interpreter/Exception.py similarity index 100% rename from src/Exscript/interpreter/Exception.py rename to Exscript/interpreter/Exception.py diff --git a/src/Exscript/interpreter/Execute.py b/Exscript/interpreter/Execute.py similarity index 100% rename from src/Exscript/interpreter/Execute.py rename to Exscript/interpreter/Execute.py diff --git a/src/Exscript/interpreter/Expression.py b/Exscript/interpreter/Expression.py similarity index 100% rename from src/Exscript/interpreter/Expression.py rename to Exscript/interpreter/Expression.py diff --git a/src/Exscript/interpreter/ExpressionNode.py b/Exscript/interpreter/ExpressionNode.py similarity index 100% rename from src/Exscript/interpreter/ExpressionNode.py rename to Exscript/interpreter/ExpressionNode.py diff --git a/src/Exscript/interpreter/Extract.py b/Exscript/interpreter/Extract.py similarity index 100% rename from src/Exscript/interpreter/Extract.py rename to Exscript/interpreter/Extract.py diff --git a/src/Exscript/interpreter/Fail.py b/Exscript/interpreter/Fail.py similarity index 100% rename from src/Exscript/interpreter/Fail.py rename to Exscript/interpreter/Fail.py diff --git a/src/Exscript/interpreter/FunctionCall.py b/Exscript/interpreter/FunctionCall.py similarity index 100% rename from src/Exscript/interpreter/FunctionCall.py rename to Exscript/interpreter/FunctionCall.py diff --git a/src/Exscript/interpreter/IfCondition.py b/Exscript/interpreter/IfCondition.py similarity index 100% rename from src/Exscript/interpreter/IfCondition.py rename to Exscript/interpreter/IfCondition.py diff --git a/src/Exscript/interpreter/Loop.py b/Exscript/interpreter/Loop.py similarity index 100% rename from src/Exscript/interpreter/Loop.py rename to Exscript/interpreter/Loop.py diff --git a/src/Exscript/interpreter/Number.py b/Exscript/interpreter/Number.py similarity index 100% rename from src/Exscript/interpreter/Number.py rename to Exscript/interpreter/Number.py diff --git a/src/Exscript/interpreter/Parser.py b/Exscript/interpreter/Parser.py similarity index 100% rename from src/Exscript/interpreter/Parser.py rename to Exscript/interpreter/Parser.py diff --git a/src/Exscript/interpreter/Program.py b/Exscript/interpreter/Program.py similarity index 100% rename from src/Exscript/interpreter/Program.py rename to Exscript/interpreter/Program.py diff --git a/src/Exscript/interpreter/Regex.py b/Exscript/interpreter/Regex.py similarity index 100% rename from src/Exscript/interpreter/Regex.py rename to Exscript/interpreter/Regex.py diff --git a/src/Exscript/interpreter/Scope.py b/Exscript/interpreter/Scope.py similarity index 100% rename from src/Exscript/interpreter/Scope.py rename to Exscript/interpreter/Scope.py diff --git a/src/Exscript/interpreter/String.py b/Exscript/interpreter/String.py similarity index 100% rename from src/Exscript/interpreter/String.py rename to Exscript/interpreter/String.py diff --git a/src/Exscript/interpreter/Template.py b/Exscript/interpreter/Template.py similarity index 100% rename from src/Exscript/interpreter/Template.py rename to Exscript/interpreter/Template.py diff --git a/src/Exscript/interpreter/Term.py b/Exscript/interpreter/Term.py similarity index 100% rename from src/Exscript/interpreter/Term.py rename to Exscript/interpreter/Term.py diff --git a/src/Exscript/interpreter/Try.py b/Exscript/interpreter/Try.py similarity index 100% rename from src/Exscript/interpreter/Try.py rename to Exscript/interpreter/Try.py diff --git a/src/Exscript/interpreter/Variable.py b/Exscript/interpreter/Variable.py similarity index 100% rename from src/Exscript/interpreter/Variable.py rename to Exscript/interpreter/Variable.py diff --git a/src/Exscript/interpreter/__init__.py b/Exscript/interpreter/__init__.py similarity index 100% rename from src/Exscript/interpreter/__init__.py rename to Exscript/interpreter/__init__.py diff --git a/src/Exscript/parselib/Exception.py b/Exscript/parselib/Exception.py similarity index 100% rename from src/Exscript/parselib/Exception.py rename to Exscript/parselib/Exception.py diff --git a/src/Exscript/parselib/Lexer.py b/Exscript/parselib/Lexer.py similarity index 100% rename from src/Exscript/parselib/Lexer.py rename to Exscript/parselib/Lexer.py diff --git a/src/Exscript/parselib/Token.py b/Exscript/parselib/Token.py similarity index 100% rename from src/Exscript/parselib/Token.py rename to Exscript/parselib/Token.py diff --git a/src/Exscript/parselib/__init__.py b/Exscript/parselib/__init__.py similarity index 100% rename from src/Exscript/parselib/__init__.py rename to Exscript/parselib/__init__.py diff --git a/src/Exscript/protocols/Dummy.py b/Exscript/protocols/Dummy.py similarity index 100% rename from src/Exscript/protocols/Dummy.py rename to Exscript/protocols/Dummy.py diff --git a/src/Exscript/protocols/Exception.py b/Exscript/protocols/Exception.py similarity index 100% rename from src/Exscript/protocols/Exception.py rename to Exscript/protocols/Exception.py diff --git a/src/Exscript/protocols/OsGuesser.py b/Exscript/protocols/OsGuesser.py similarity index 100% rename from src/Exscript/protocols/OsGuesser.py rename to Exscript/protocols/OsGuesser.py diff --git a/src/Exscript/protocols/Protocol.py b/Exscript/protocols/Protocol.py similarity index 100% rename from src/Exscript/protocols/Protocol.py rename to Exscript/protocols/Protocol.py diff --git a/src/Exscript/protocols/SSH2.py b/Exscript/protocols/SSH2.py similarity index 100% rename from src/Exscript/protocols/SSH2.py rename to Exscript/protocols/SSH2.py diff --git a/src/Exscript/protocols/Telnet.py b/Exscript/protocols/Telnet.py similarity index 100% rename from src/Exscript/protocols/Telnet.py rename to Exscript/protocols/Telnet.py diff --git a/src/Exscript/protocols/__init__.py b/Exscript/protocols/__init__.py similarity index 100% rename from src/Exscript/protocols/__init__.py rename to Exscript/protocols/__init__.py diff --git a/src/Exscript/protocols/drivers/__init__.py b/Exscript/protocols/drivers/__init__.py similarity index 100% rename from src/Exscript/protocols/drivers/__init__.py rename to Exscript/protocols/drivers/__init__.py diff --git a/src/Exscript/protocols/drivers/ace.py b/Exscript/protocols/drivers/ace.py similarity index 100% rename from src/Exscript/protocols/drivers/ace.py rename to Exscript/protocols/drivers/ace.py diff --git a/src/Exscript/protocols/drivers/aironet.py b/Exscript/protocols/drivers/aironet.py similarity index 100% rename from src/Exscript/protocols/drivers/aironet.py rename to Exscript/protocols/drivers/aironet.py diff --git a/src/Exscript/protocols/drivers/aix.py b/Exscript/protocols/drivers/aix.py similarity index 100% rename from src/Exscript/protocols/drivers/aix.py rename to Exscript/protocols/drivers/aix.py diff --git a/src/Exscript/protocols/drivers/arbor_peakflow.py b/Exscript/protocols/drivers/arbor_peakflow.py similarity index 100% rename from src/Exscript/protocols/drivers/arbor_peakflow.py rename to Exscript/protocols/drivers/arbor_peakflow.py diff --git a/src/Exscript/protocols/drivers/aruba.py b/Exscript/protocols/drivers/aruba.py similarity index 100% rename from src/Exscript/protocols/drivers/aruba.py rename to Exscript/protocols/drivers/aruba.py diff --git a/src/Exscript/protocols/drivers/bigip.py b/Exscript/protocols/drivers/bigip.py similarity index 100% rename from src/Exscript/protocols/drivers/bigip.py rename to Exscript/protocols/drivers/bigip.py diff --git a/src/Exscript/protocols/drivers/brocade.py b/Exscript/protocols/drivers/brocade.py similarity index 100% rename from src/Exscript/protocols/drivers/brocade.py rename to Exscript/protocols/drivers/brocade.py diff --git a/src/Exscript/protocols/drivers/driver.py b/Exscript/protocols/drivers/driver.py similarity index 100% rename from src/Exscript/protocols/drivers/driver.py rename to Exscript/protocols/drivers/driver.py diff --git a/src/Exscript/protocols/drivers/enterasys.py b/Exscript/protocols/drivers/enterasys.py similarity index 100% rename from src/Exscript/protocols/drivers/enterasys.py rename to Exscript/protocols/drivers/enterasys.py diff --git a/src/Exscript/protocols/drivers/enterasys_wc.py b/Exscript/protocols/drivers/enterasys_wc.py similarity index 100% rename from src/Exscript/protocols/drivers/enterasys_wc.py rename to Exscript/protocols/drivers/enterasys_wc.py diff --git a/src/Exscript/protocols/drivers/eos.py b/Exscript/protocols/drivers/eos.py similarity index 100% rename from src/Exscript/protocols/drivers/eos.py rename to Exscript/protocols/drivers/eos.py diff --git a/src/Exscript/protocols/drivers/ericsson_ban.py b/Exscript/protocols/drivers/ericsson_ban.py similarity index 100% rename from src/Exscript/protocols/drivers/ericsson_ban.py rename to Exscript/protocols/drivers/ericsson_ban.py diff --git a/src/Exscript/protocols/drivers/fortios.py b/Exscript/protocols/drivers/fortios.py similarity index 100% rename from src/Exscript/protocols/drivers/fortios.py rename to Exscript/protocols/drivers/fortios.py diff --git a/src/Exscript/protocols/drivers/generic.py b/Exscript/protocols/drivers/generic.py similarity index 100% rename from src/Exscript/protocols/drivers/generic.py rename to Exscript/protocols/drivers/generic.py diff --git a/src/Exscript/protocols/drivers/hp_pro_curve.py b/Exscript/protocols/drivers/hp_pro_curve.py similarity index 100% rename from src/Exscript/protocols/drivers/hp_pro_curve.py rename to Exscript/protocols/drivers/hp_pro_curve.py diff --git a/src/Exscript/protocols/drivers/ios.py b/Exscript/protocols/drivers/ios.py similarity index 100% rename from src/Exscript/protocols/drivers/ios.py rename to Exscript/protocols/drivers/ios.py diff --git a/src/Exscript/protocols/drivers/ios_xr.py b/Exscript/protocols/drivers/ios_xr.py similarity index 100% rename from src/Exscript/protocols/drivers/ios_xr.py rename to Exscript/protocols/drivers/ios_xr.py diff --git a/src/Exscript/protocols/drivers/isam.py b/Exscript/protocols/drivers/isam.py similarity index 100% rename from src/Exscript/protocols/drivers/isam.py rename to Exscript/protocols/drivers/isam.py diff --git a/src/Exscript/protocols/drivers/junos.py b/Exscript/protocols/drivers/junos.py similarity index 100% rename from src/Exscript/protocols/drivers/junos.py rename to Exscript/protocols/drivers/junos.py diff --git a/src/Exscript/protocols/drivers/junos_erx.py b/Exscript/protocols/drivers/junos_erx.py similarity index 100% rename from src/Exscript/protocols/drivers/junos_erx.py rename to Exscript/protocols/drivers/junos_erx.py diff --git a/src/Exscript/protocols/drivers/mrv.py b/Exscript/protocols/drivers/mrv.py similarity index 100% rename from src/Exscript/protocols/drivers/mrv.py rename to Exscript/protocols/drivers/mrv.py diff --git a/src/Exscript/protocols/drivers/nxos.py b/Exscript/protocols/drivers/nxos.py similarity index 100% rename from src/Exscript/protocols/drivers/nxos.py rename to Exscript/protocols/drivers/nxos.py diff --git a/src/Exscript/protocols/drivers/one_os.py b/Exscript/protocols/drivers/one_os.py similarity index 100% rename from src/Exscript/protocols/drivers/one_os.py rename to Exscript/protocols/drivers/one_os.py diff --git a/src/Exscript/protocols/drivers/rios.py b/Exscript/protocols/drivers/rios.py similarity index 100% rename from src/Exscript/protocols/drivers/rios.py rename to Exscript/protocols/drivers/rios.py diff --git a/src/Exscript/protocols/drivers/shell.py b/Exscript/protocols/drivers/shell.py similarity index 100% rename from src/Exscript/protocols/drivers/shell.py rename to Exscript/protocols/drivers/shell.py diff --git a/src/Exscript/protocols/drivers/smart_edge_os.py b/Exscript/protocols/drivers/smart_edge_os.py similarity index 100% rename from src/Exscript/protocols/drivers/smart_edge_os.py rename to Exscript/protocols/drivers/smart_edge_os.py diff --git a/src/Exscript/protocols/drivers/sros.py b/Exscript/protocols/drivers/sros.py similarity index 100% rename from src/Exscript/protocols/drivers/sros.py rename to Exscript/protocols/drivers/sros.py diff --git a/src/Exscript/protocols/drivers/vrp.py b/Exscript/protocols/drivers/vrp.py similarity index 100% rename from src/Exscript/protocols/drivers/vrp.py rename to Exscript/protocols/drivers/vrp.py diff --git a/src/Exscript/protocols/drivers/vxworks.py b/Exscript/protocols/drivers/vxworks.py similarity index 100% rename from src/Exscript/protocols/drivers/vxworks.py rename to Exscript/protocols/drivers/vxworks.py diff --git a/src/Exscript/protocols/drivers/zte.py b/Exscript/protocols/drivers/zte.py similarity index 100% rename from src/Exscript/protocols/drivers/zte.py rename to Exscript/protocols/drivers/zte.py diff --git a/src/Exscript/protocols/telnetlib.py b/Exscript/protocols/telnetlib.py similarity index 100% rename from src/Exscript/protocols/telnetlib.py rename to Exscript/protocols/telnetlib.py diff --git a/src/Exscript/servers/HTTPd.py b/Exscript/servers/HTTPd.py similarity index 100% rename from src/Exscript/servers/HTTPd.py rename to Exscript/servers/HTTPd.py diff --git a/src/Exscript/servers/SSHd.py b/Exscript/servers/SSHd.py similarity index 100% rename from src/Exscript/servers/SSHd.py rename to Exscript/servers/SSHd.py diff --git a/src/Exscript/servers/Server.py b/Exscript/servers/Server.py similarity index 100% rename from src/Exscript/servers/Server.py rename to Exscript/servers/Server.py diff --git a/src/Exscript/servers/Telnetd.py b/Exscript/servers/Telnetd.py similarity index 100% rename from src/Exscript/servers/Telnetd.py rename to Exscript/servers/Telnetd.py diff --git a/src/Exscript/servers/__init__.py b/Exscript/servers/__init__.py similarity index 100% rename from src/Exscript/servers/__init__.py rename to Exscript/servers/__init__.py diff --git a/src/Exscript/stdlib/__init__.py b/Exscript/stdlib/__init__.py similarity index 100% rename from src/Exscript/stdlib/__init__.py rename to Exscript/stdlib/__init__.py diff --git a/src/Exscript/stdlib/connection.py b/Exscript/stdlib/connection.py similarity index 100% rename from src/Exscript/stdlib/connection.py rename to Exscript/stdlib/connection.py diff --git a/src/Exscript/stdlib/crypt.py b/Exscript/stdlib/crypt.py similarity index 100% rename from src/Exscript/stdlib/crypt.py rename to Exscript/stdlib/crypt.py diff --git a/src/Exscript/stdlib/file.py b/Exscript/stdlib/file.py similarity index 100% rename from src/Exscript/stdlib/file.py rename to Exscript/stdlib/file.py diff --git a/src/Exscript/stdlib/ipv4.py b/Exscript/stdlib/ipv4.py similarity index 100% rename from src/Exscript/stdlib/ipv4.py rename to Exscript/stdlib/ipv4.py diff --git a/src/Exscript/stdlib/list.py b/Exscript/stdlib/list.py similarity index 100% rename from src/Exscript/stdlib/list.py rename to Exscript/stdlib/list.py diff --git a/src/Exscript/stdlib/mysys.py b/Exscript/stdlib/mysys.py similarity index 100% rename from src/Exscript/stdlib/mysys.py rename to Exscript/stdlib/mysys.py diff --git a/src/Exscript/stdlib/string.py b/Exscript/stdlib/string.py similarity index 100% rename from src/Exscript/stdlib/string.py rename to Exscript/stdlib/string.py diff --git a/src/Exscript/stdlib/util.py b/Exscript/stdlib/util.py similarity index 100% rename from src/Exscript/stdlib/util.py rename to Exscript/stdlib/util.py diff --git a/src/Exscript/util/__init__.py b/Exscript/util/__init__.py similarity index 100% rename from src/Exscript/util/__init__.py rename to Exscript/util/__init__.py diff --git a/src/Exscript/util/buffer.py b/Exscript/util/buffer.py similarity index 100% rename from src/Exscript/util/buffer.py rename to Exscript/util/buffer.py diff --git a/src/Exscript/util/cast.py b/Exscript/util/cast.py similarity index 100% rename from src/Exscript/util/cast.py rename to Exscript/util/cast.py diff --git a/src/Exscript/util/crypt.py b/Exscript/util/crypt.py similarity index 100% rename from src/Exscript/util/crypt.py rename to Exscript/util/crypt.py diff --git a/src/Exscript/util/daemonize.py b/Exscript/util/daemonize.py similarity index 100% rename from src/Exscript/util/daemonize.py rename to Exscript/util/daemonize.py diff --git a/src/Exscript/util/decorator.py b/Exscript/util/decorator.py similarity index 100% rename from src/Exscript/util/decorator.py rename to Exscript/util/decorator.py diff --git a/src/Exscript/util/event.py b/Exscript/util/event.py similarity index 100% rename from src/Exscript/util/event.py rename to Exscript/util/event.py diff --git a/src/Exscript/util/file.py b/Exscript/util/file.py similarity index 100% rename from src/Exscript/util/file.py rename to Exscript/util/file.py diff --git a/src/Exscript/util/impl.py b/Exscript/util/impl.py similarity index 100% rename from src/Exscript/util/impl.py rename to Exscript/util/impl.py diff --git a/src/Exscript/util/interact.py b/Exscript/util/interact.py similarity index 100% rename from src/Exscript/util/interact.py rename to Exscript/util/interact.py diff --git a/src/Exscript/util/ip.py b/Exscript/util/ip.py similarity index 100% rename from src/Exscript/util/ip.py rename to Exscript/util/ip.py diff --git a/src/Exscript/util/ipv4.py b/Exscript/util/ipv4.py similarity index 100% rename from src/Exscript/util/ipv4.py rename to Exscript/util/ipv4.py diff --git a/src/Exscript/util/ipv6.py b/Exscript/util/ipv6.py similarity index 100% rename from src/Exscript/util/ipv6.py rename to Exscript/util/ipv6.py diff --git a/src/Exscript/util/log.py b/Exscript/util/log.py similarity index 100% rename from src/Exscript/util/log.py rename to Exscript/util/log.py diff --git a/src/Exscript/util/mail.py b/Exscript/util/mail.py similarity index 100% rename from src/Exscript/util/mail.py rename to Exscript/util/mail.py diff --git a/src/Exscript/util/match.py b/Exscript/util/match.py similarity index 100% rename from src/Exscript/util/match.py rename to Exscript/util/match.py diff --git a/src/Exscript/util/pidutil.py b/Exscript/util/pidutil.py similarity index 100% rename from src/Exscript/util/pidutil.py rename to Exscript/util/pidutil.py diff --git a/src/Exscript/util/report.py b/Exscript/util/report.py similarity index 100% rename from src/Exscript/util/report.py rename to Exscript/util/report.py diff --git a/src/Exscript/util/sigint.py b/Exscript/util/sigint.py similarity index 100% rename from src/Exscript/util/sigint.py rename to Exscript/util/sigint.py diff --git a/src/Exscript/util/sigintcatcher.py b/Exscript/util/sigintcatcher.py similarity index 100% rename from src/Exscript/util/sigintcatcher.py rename to Exscript/util/sigintcatcher.py diff --git a/src/Exscript/util/start.py b/Exscript/util/start.py similarity index 100% rename from src/Exscript/util/start.py rename to Exscript/util/start.py diff --git a/src/Exscript/util/syslog.py b/Exscript/util/syslog.py similarity index 100% rename from src/Exscript/util/syslog.py rename to Exscript/util/syslog.py diff --git a/src/Exscript/util/template.py b/Exscript/util/template.py similarity index 100% rename from src/Exscript/util/template.py rename to Exscript/util/template.py diff --git a/src/Exscript/util/tty.py b/Exscript/util/tty.py similarity index 100% rename from src/Exscript/util/tty.py rename to Exscript/util/tty.py diff --git a/src/Exscript/util/url.py b/Exscript/util/url.py similarity index 100% rename from src/Exscript/util/url.py rename to Exscript/util/url.py diff --git a/src/Exscript/util/weakmethod.py b/Exscript/util/weakmethod.py similarity index 100% rename from src/Exscript/util/weakmethod.py rename to Exscript/util/weakmethod.py diff --git a/src/Exscript/version.py b/Exscript/version.py similarity index 100% rename from src/Exscript/version.py rename to Exscript/version.py diff --git a/src/Exscript/workqueue/DBPipeline.py b/Exscript/workqueue/DBPipeline.py similarity index 100% rename from src/Exscript/workqueue/DBPipeline.py rename to Exscript/workqueue/DBPipeline.py diff --git a/src/Exscript/workqueue/Job.py b/Exscript/workqueue/Job.py similarity index 100% rename from src/Exscript/workqueue/Job.py rename to Exscript/workqueue/Job.py diff --git a/src/Exscript/workqueue/MainLoop.py b/Exscript/workqueue/MainLoop.py similarity index 100% rename from src/Exscript/workqueue/MainLoop.py rename to Exscript/workqueue/MainLoop.py diff --git a/src/Exscript/workqueue/Pipeline.py b/Exscript/workqueue/Pipeline.py similarity index 100% rename from src/Exscript/workqueue/Pipeline.py rename to Exscript/workqueue/Pipeline.py diff --git a/src/Exscript/workqueue/Task.py b/Exscript/workqueue/Task.py similarity index 100% rename from src/Exscript/workqueue/Task.py rename to Exscript/workqueue/Task.py diff --git a/src/Exscript/workqueue/WorkQueue.py b/Exscript/workqueue/WorkQueue.py similarity index 100% rename from src/Exscript/workqueue/WorkQueue.py rename to Exscript/workqueue/WorkQueue.py diff --git a/src/Exscript/workqueue/__init__.py b/Exscript/workqueue/__init__.py similarity index 100% rename from src/Exscript/workqueue/__init__.py rename to Exscript/workqueue/__init__.py diff --git a/Makefile b/Makefile index f631b479..27539606 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DISTDIR=/pub/code/releases/$(NAME) .PHONY : clean clean: find . -name "*.pyc" -o -name "*.pyo" | xargs -n1 rm -f - rm -Rf build src/*.egg-info + rm -Rf build *.egg-info cd doc; make clean .PHONY : dist-clean diff --git a/doc/Makefile b/doc/Makefile index f8fa6183..a999cb9c 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -21,7 +21,7 @@ pdf: # Copy the stdlib Python directory and modify it such that the # generated documentation does not leak implementation details. rm -Rf latex stdlib - cp -r ../src/Exscript/stdlib . + cp -r ../Exscript/stdlib . mv stdlib/mysys.py stdlib/sys.py sed -i 's/\(def [^(]*(\)scope,? ?/\1/' stdlib/*.py # Remove the scope arg sed -i 's/\(def [^(]*\)_(/\1(/' stdlib/*.py # Remove trailing _ @@ -61,7 +61,29 @@ handbook: pdf apidocs: cd ..; ./version.sh - python mkapidoc.py + epydoc --name Exscript \ + --exclude ^Exscript\.AccountManager$ \ + --exclude ^Exscript\.Log$ \ + --exclude ^Exscript\.Logfile$ \ + --exclude ^Exscript\.LoggerProxy$ \ + --exclude ^Exscript\.external$ \ + --exclude ^Exscript\.interpreter$ \ + --exclude ^Exscript\.parselib$ \ + --exclude ^Exscript\.protocols\.OsGuesser$ \ + --exclude ^Exscript\.protocols\.telnetlib$ \ + --exclude ^Exscript\.stdlib$ \ + --exclude ^Exscript\.workqueue$ \ + --exclude ^Exscript\.version$ \ + --exclude-introspect ^Exscript\.util\.sigintcatcher$ \ + --html \ + --no-private \ + --introspect-only \ + --no-source \ + --no-frames \ + --inheritance=included \ + -v \ + -o doc/api \ + Exscript cd ..; ./version.sh --reset clean: diff --git a/doc/mkapidoc.py b/doc/mkapidoc.py deleted file mode 100644 index 6e18da55..00000000 --- a/doc/mkapidoc.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python -# Generates the *public* API documentation. -# Remember to hide your private parts, people! -import os, re, sys - -project = 'Exscript' -base_dir = os.path.join('..', 'src') -doc_dir = 'api' - -# Create the documentation directory. -if not os.path.exists(doc_dir): - os.makedirs(doc_dir) - -# Generate the API documentation. -cmd = 'epydoc ' + ' '.join(['--name', project, - r'--exclude ^Exscript\.AccountManager$', - r'--exclude ^Exscript\.Log$', - r'--exclude ^Exscript\.Logfile$', - r'--exclude ^Exscript\.LoggerProxy$', - r'--exclude ^Exscript\.external$', - r'--exclude ^Exscript\.interpreter$', - r'--exclude ^Exscript\.parselib$', - r'--exclude ^Exscript\.protocols\.OsGuesser$', - r'--exclude ^Exscript\.protocols\.telnetlib$', - r'--exclude ^Exscript\.stdlib$', - r'--exclude ^Exscript\.workqueue$', - r'--exclude ^Exscript\.version$', - r'--exclude-introspect ^Exscript\.util\.sigintcatcher$', - '--html', - '--no-private', - '--introspect-only', - '--no-source', - '--no-frames', - '--inheritance=included', - '-v', - '-o %s' % doc_dir, - os.path.join(base_dir, project)]) -print cmd -os.system(cmd) diff --git a/setup.py b/setup.py index 8481e183..5514e1d6 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,7 @@ import sys import os from setuptools import setup, find_packages - -# Import the file that contains the version number. -exscript_dir = os.path.join(os.path.dirname(__file__), 'src', 'Exscript') -sys.path.insert(0, exscript_dir) -from version import __version__ +from Exscript.version import __version__ # Import the project description from the README. readme = open('README.md').read() @@ -21,10 +17,9 @@ author = 'Samuel Abels', author_email = 'knipknap@gmail.com', license = 'GPLv2', - package_dir = {'': 'src', - 'Exscript': os.path.join('src', 'Exscript')}, + package_dir = {'Exscript': 'Exscript'}, package_data = {}, - packages = find_packages('src'), + packages = find_packages(), scripts = ['exscript'], install_requires = ['paramiko', 'pycrypto'], extras_require = {}, diff --git a/tests/Exscript/AccountManagerTest.py b/tests/Exscript/AccountManagerTest.py index 74622e43..151ee00e 100644 --- a/tests/Exscript/AccountManagerTest.py +++ b/tests/Exscript/AccountManagerTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path, warnings -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) from Exscript.Account import Account from Exscript.AccountPool import AccountPool diff --git a/tests/Exscript/AccountPoolTest.py b/tests/Exscript/AccountPoolTest.py index 6579309c..a0308d37 100644 --- a/tests/Exscript/AccountPoolTest.py +++ b/tests/Exscript/AccountPoolTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) from Exscript import Account from Exscript.AccountPool import AccountPool diff --git a/tests/Exscript/AccountTest.py b/tests/Exscript/AccountTest.py index 150b58b1..cc7f4cfe 100644 --- a/tests/Exscript/AccountTest.py +++ b/tests/Exscript/AccountTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) from Exscript import Account, PrivateKey diff --git a/tests/Exscript/FileLoggerTest.py b/tests/Exscript/FileLoggerTest.py index c0946f69..96191f65 100644 --- a/tests/Exscript/FileLoggerTest.py +++ b/tests/Exscript/FileLoggerTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) from tempfile import mkdtemp from shutil import rmtree diff --git a/tests/Exscript/HostTest.py b/tests/Exscript/HostTest.py index d865e6bb..cb9c85f5 100644 --- a/tests/Exscript/HostTest.py +++ b/tests/Exscript/HostTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) from Exscript import Host, Account from Exscript.util.url import Url diff --git a/tests/Exscript/LogTest.py b/tests/Exscript/LogTest.py index 0dccb8f1..c60379fd 100644 --- a/tests/Exscript/LogTest.py +++ b/tests/Exscript/LogTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) from Exscript.Log import Log from Exscript import Host diff --git a/tests/Exscript/LogfileTest.py b/tests/Exscript/LogfileTest.py index 342bb2df..fb216292 100644 --- a/tests/Exscript/LogfileTest.py +++ b/tests/Exscript/LogfileTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) from tempfile import mkdtemp from shutil import rmtree diff --git a/tests/Exscript/LoggerTest.py b/tests/Exscript/LoggerTest.py index 5de4ab7b..31d60815 100644 --- a/tests/Exscript/LoggerTest.py +++ b/tests/Exscript/LoggerTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) import gc from itertools import islice diff --git a/tests/Exscript/PrivateKeyTest.py b/tests/Exscript/PrivateKeyTest.py index 5a57225d..90ba0fe7 100644 --- a/tests/Exscript/PrivateKeyTest.py +++ b/tests/Exscript/PrivateKeyTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) from Exscript import PrivateKey diff --git a/tests/Exscript/QueueTest.py b/tests/Exscript/QueueTest.py index 5a920cb2..d8de9fbc 100644 --- a/tests/Exscript/QueueTest.py +++ b/tests/Exscript/QueueTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path, warnings -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) warnings.simplefilter('ignore', DeprecationWarning) diff --git a/tests/Exscript/TemplateTest.py b/tests/Exscript/TemplateTest.py index 71800941..dbb97c88 100644 --- a/tests/Exscript/TemplateTest.py +++ b/tests/Exscript/TemplateTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) from Exscript import Queue, Account, Logger, protocols from Exscript.util import template diff --git a/tests/Exscript/emulators/CommandSetTest.py b/tests/Exscript/emulators/CommandSetTest.py index 68f378d2..eb1a0223 100644 --- a/tests/Exscript/emulators/CommandSetTest.py +++ b/tests/Exscript/emulators/CommandSetTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) from Exscript.emulators import CommandSet diff --git a/tests/Exscript/emulators/IOSEmulatorTest.py b/tests/Exscript/emulators/IOSEmulatorTest.py index d8465662..8dfb3749 100644 --- a/tests/Exscript/emulators/IOSEmulatorTest.py +++ b/tests/Exscript/emulators/IOSEmulatorTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) from VirtualDeviceTest import VirtualDeviceTest from Exscript.emulators import IOSEmulator diff --git a/tests/Exscript/emulators/VirtualDeviceTest.py b/tests/Exscript/emulators/VirtualDeviceTest.py index 88ff1d88..3a9f3dae 100644 --- a/tests/Exscript/emulators/VirtualDeviceTest.py +++ b/tests/Exscript/emulators/VirtualDeviceTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) from Exscript.emulators import VirtualDevice diff --git a/tests/Exscript/protocols/DummyTest.py b/tests/Exscript/protocols/DummyTest.py index 91a14010..9786cccd 100644 --- a/tests/Exscript/protocols/DummyTest.py +++ b/tests/Exscript/protocols/DummyTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) from ProtocolTest import ProtocolTest from Exscript.emulators import VirtualDevice diff --git a/tests/Exscript/protocols/OsGuesserTest.py b/tests/Exscript/protocols/OsGuesserTest.py index 8a4b156e..7a4c5360 100644 --- a/tests/Exscript/protocols/OsGuesserTest.py +++ b/tests/Exscript/protocols/OsGuesserTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) from Exscript.protocols.OsGuesser import OsGuesser import Exscript.protocols.drivers as drivers diff --git a/tests/Exscript/protocols/ProtocolTest.py b/tests/Exscript/protocols/ProtocolTest.py index 516d8312..7f3ec097 100644 --- a/tests/Exscript/protocols/ProtocolTest.py +++ b/tests/Exscript/protocols/ProtocolTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) import time from functools import partial diff --git a/tests/Exscript/protocols/SSH2Test.py b/tests/Exscript/protocols/SSH2Test.py index 0c03743a..236d3a46 100644 --- a/tests/Exscript/protocols/SSH2Test.py +++ b/tests/Exscript/protocols/SSH2Test.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) from ProtocolTest import ProtocolTest from Exscript.servers import SSHd diff --git a/tests/Exscript/protocols/TelnetTest.py b/tests/Exscript/protocols/TelnetTest.py index 50135998..fed2a25e 100644 --- a/tests/Exscript/protocols/TelnetTest.py +++ b/tests/Exscript/protocols/TelnetTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os, time -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) from ProtocolTest import ProtocolTest from Exscript import Account diff --git a/tests/Exscript/servers/SSHdTest.py b/tests/Exscript/servers/SSHdTest.py index 18ea6063..01c30214 100644 --- a/tests/Exscript/servers/SSHdTest.py +++ b/tests/Exscript/servers/SSHdTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) from ServerTest import ServerTest from Exscript.servers import SSHd diff --git a/tests/Exscript/servers/ServerTest.py b/tests/Exscript/servers/ServerTest.py index bff72e47..125c454c 100644 --- a/tests/Exscript/servers/ServerTest.py +++ b/tests/Exscript/servers/ServerTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) import time from Exscript import Account diff --git a/tests/Exscript/servers/TelnetdTest.py b/tests/Exscript/servers/TelnetdTest.py index 725ebd89..e579561a 100644 --- a/tests/Exscript/servers/TelnetdTest.py +++ b/tests/Exscript/servers/TelnetdTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) from ServerTest import ServerTest from Exscript.servers import Telnetd diff --git a/tests/Exscript/util/bufferTest.py b/tests/Exscript/util/bufferTest.py index d3820921..93c5dbfd 100644 --- a/tests/Exscript/util/bufferTest.py +++ b/tests/Exscript/util/bufferTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) from tempfile import TemporaryFile from functools import partial diff --git a/tests/Exscript/util/castTest.py b/tests/Exscript/util/castTest.py index d85e0788..04466bd6 100644 --- a/tests/Exscript/util/castTest.py +++ b/tests/Exscript/util/castTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) import Exscript.util.cast import re diff --git a/tests/Exscript/util/cryptTest.py b/tests/Exscript/util/cryptTest.py index 3548338d..805481a8 100644 --- a/tests/Exscript/util/cryptTest.py +++ b/tests/Exscript/util/cryptTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) import Exscript.util.crypt diff --git a/tests/Exscript/util/decoratorTest.py b/tests/Exscript/util/decoratorTest.py index c0acdc3c..952d0127 100644 --- a/tests/Exscript/util/decoratorTest.py +++ b/tests/Exscript/util/decoratorTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) from functools import partial import Exscript.util.decorator diff --git a/tests/Exscript/util/eventTest.py b/tests/Exscript/util/eventTest.py index e9ee75f6..cfa2619f 100644 --- a/tests/Exscript/util/eventTest.py +++ b/tests/Exscript/util/eventTest.py @@ -2,7 +2,7 @@ import unittest import re import os -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) from Exscript.util.event import Event diff --git a/tests/Exscript/util/fileTest.py b/tests/Exscript/util/fileTest.py index d009a1a7..5d46b682 100644 --- a/tests/Exscript/util/fileTest.py +++ b/tests/Exscript/util/fileTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) import base64 import Exscript.util.file diff --git a/tests/Exscript/util/interactTest.py b/tests/Exscript/util/interactTest.py index da8b3093..9b9d4e07 100644 --- a/tests/Exscript/util/interactTest.py +++ b/tests/Exscript/util/interactTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) from tempfile import NamedTemporaryFile import Exscript.util.interact diff --git a/tests/Exscript/util/ipTest.py b/tests/Exscript/util/ipTest.py index c3ce9f50..a6490bf5 100644 --- a/tests/Exscript/util/ipTest.py +++ b/tests/Exscript/util/ipTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) import Exscript.util.ip diff --git a/tests/Exscript/util/ipv4Test.py b/tests/Exscript/util/ipv4Test.py index 8b08df1d..53fd705b 100644 --- a/tests/Exscript/util/ipv4Test.py +++ b/tests/Exscript/util/ipv4Test.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) import Exscript.util.ipv4 diff --git a/tests/Exscript/util/ipv6Test.py b/tests/Exscript/util/ipv6Test.py index 935506ab..7a5838be 100644 --- a/tests/Exscript/util/ipv6Test.py +++ b/tests/Exscript/util/ipv6Test.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) import Exscript.util.ipv6 diff --git a/tests/Exscript/util/mailTest.py b/tests/Exscript/util/mailTest.py index 3b0a7d44..ef1511b8 100644 --- a/tests/Exscript/util/mailTest.py +++ b/tests/Exscript/util/mailTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path, tempfile -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) from getpass import getuser import Exscript.util.mail diff --git a/tests/Exscript/util/matchTest.py b/tests/Exscript/util/matchTest.py index e9d58fad..63c3277c 100644 --- a/tests/Exscript/util/matchTest.py +++ b/tests/Exscript/util/matchTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) import Exscript.util.match diff --git a/tests/Exscript/util/reportTest.py b/tests/Exscript/util/reportTest.py index 6c4b5bc2..d3760cb6 100644 --- a/tests/Exscript/util/reportTest.py +++ b/tests/Exscript/util/reportTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) import traceback import Exscript.util.report diff --git a/tests/Exscript/util/startTest.py b/tests/Exscript/util/startTest.py index e59bf0b4..447f5e75 100644 --- a/tests/Exscript/util/startTest.py +++ b/tests/Exscript/util/startTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) import Exscript import Exscript.util.start diff --git a/tests/Exscript/util/syslogTest.py b/tests/Exscript/util/syslogTest.py index a1365c8f..4c8b4961 100644 --- a/tests/Exscript/util/syslogTest.py +++ b/tests/Exscript/util/syslogTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) import Exscript.util.syslog diff --git a/tests/Exscript/util/ttyTest.py b/tests/Exscript/util/ttyTest.py index df9d7197..ca80143f 100644 --- a/tests/Exscript/util/ttyTest.py +++ b/tests/Exscript/util/ttyTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) import tempfile import Exscript.util.tty diff --git a/tests/Exscript/util/urlTest.py b/tests/Exscript/util/urlTest.py index c6b9abba..9f5a39ad 100644 --- a/tests/Exscript/util/urlTest.py +++ b/tests/Exscript/util/urlTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) from Exscript.util.url import Url diff --git a/tests/Exscript/util/weakmethodTest.py b/tests/Exscript/util/weakmethodTest.py index b52e326a..5fb7e426 100644 --- a/tests/Exscript/util/weakmethodTest.py +++ b/tests/Exscript/util/weakmethodTest.py @@ -2,7 +2,7 @@ import unittest import re import os -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) from Exscript.util.weakmethod import ref, WeakMethod, DeadMethodCalled diff --git a/tests/Exscript/workqueue/JobTest.py b/tests/Exscript/workqueue/JobTest.py index 22cd868b..885c49d6 100644 --- a/tests/Exscript/workqueue/JobTest.py +++ b/tests/Exscript/workqueue/JobTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path, threading -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) from multiprocessing import Pipe from Exscript.workqueue.Job import Thread, Process, Job diff --git a/tests/Exscript/workqueue/MainLoopTest.py b/tests/Exscript/workqueue/MainLoopTest.py index 1556f063..6be73b23 100644 --- a/tests/Exscript/workqueue/MainLoopTest.py +++ b/tests/Exscript/workqueue/MainLoopTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path, threading -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) from Exscript.workqueue import MainLoop from Exscript.workqueue.Pipeline import Pipeline diff --git a/tests/Exscript/workqueue/PipelineTest.py b/tests/Exscript/workqueue/PipelineTest.py index ac753c7c..c130617b 100644 --- a/tests/Exscript/workqueue/PipelineTest.py +++ b/tests/Exscript/workqueue/PipelineTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path, threading, time -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) import time from threading import Thread diff --git a/tests/Exscript/workqueue/TaskTest.py b/tests/Exscript/workqueue/TaskTest.py index 2b1def20..e3b1880b 100644 --- a/tests/Exscript/workqueue/TaskTest.py +++ b/tests/Exscript/workqueue/TaskTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path, warnings -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) warnings.simplefilter('ignore', DeprecationWarning) diff --git a/tests/Exscript/workqueue/WorkQueueTest.py b/tests/Exscript/workqueue/WorkQueueTest.py index 234b3766..7789c4a1 100644 --- a/tests/Exscript/workqueue/WorkQueueTest.py +++ b/tests/Exscript/workqueue/WorkQueueTest.py @@ -1,5 +1,5 @@ import sys, unittest, re, os.path, threading, time -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..')) import random import time diff --git a/version.sh b/version.sh index f6e677e7..07bfb9f4 100755 --- a/version.sh +++ b/version.sh @@ -2,7 +2,7 @@ # Tag revisions like this: # $ git tag -a -m "v0.2" v0.2 VERSION_IN=VERSION.in -VERSION_FILE=src/Exscript/version.py +VERSION_FILE=Exscript/version.py # Check that we are actually in a git managed project. if [ ! -e .git -a -z "$1" ]; then From e2cfefbb9c0af032e3dfd78b8a1600e6fa015462 Mon Sep 17 00:00:00 2001 From: Samuel Abels Date: Sun, 5 Mar 2017 13:33:04 +0100 Subject: [PATCH 5/5] fix: last commit broke setup.py --- setup.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 5514e1d6..02cd9359 100644 --- a/setup.py +++ b/setup.py @@ -5,9 +5,11 @@ # Import the project description from the README. readme = open('README.md').read() -start = readme.index('\n\n') -end = readme.index('\n\nLinks') -descr = readme[start:end].strip() +descr = ''' +Exscript is a Python module and a template processor for automating network +connections over protocols such as Telnet or SSH. We attempt to create the +best possible set of tools for working with Telnet and SSH. +'''.strip() # Run the setup. setup(name = 'Exscript',