<commit_before>""" User interface code for the animation renderer.

	Author: Alastair Hughes
"""

# Local imports.
from display import preview, render
from animate import gen_render_frame
from models import Model, Values
from constants import MAX_FPS, MIN_FPS, MAX_TEXT_HEIGHT, MIN_TEXT_HEIGHT
import transforms

# Tkinter imports
import Tkinter as tk
import tkFileDialog
import ttk

# Configuration option defaults.
# File paths:
gis_files = "H:/My Documents/vis/gis/SmallPatches"
csv_dir = "H:/My Documents/vis/csv/small"
movie_filename = "H:/My Documents/vis/movie.mp4"
# Animation options:
field_of_interest = "Soil.SoilWater.Drainage" # Look at a CSV file to find this?

class InvalidOption(ValueError):
	""" Error to be raised if an option is invalid """

	def __init__(self, name, value):
		ValueError.__init__(self, \
			"Option '{}' has an invalid value of '{}'!".format(name, value))

class UI(ttk.Frame):

	def __init__(self, master, pages, *args, **kargs):
		""" Initialise self """

		self.master = master
		ttk.Frame.__init__(self, self.master, *args, **kargs)
		self.pack(expand=True, fill='both')

		# Create the tabs.
		self.windows = ttk.Notebook(self)
		for page in pages:
			self.windows.add(pages[page](self.windows), text=page)
		self.windows.pack(expand=True, fill='both')

class Options(ttk.Frame):

	def __init__(self, master):
		""" Init self """

		self.master = master
		ttk.Frame.__init__(self, self.master)

		# Options added.
		self.options = {} # name: (entry, get)

	def add_raw_option(self, name, result, default, event = "<Return>"):
		""" Add an option """

		row = self.grid_size()[1]

		# Create a wrapper callback.
		def wrapper(event):
			try:
				result(event.widget.get())
			except:
				event.widget.delete(0, 'end')

		# Create a label.
		label = ttk.Label(self, text = name + ':')
		label.grid(row = row, column = 1, sticky = 'w')

		# Create an entry.
		#TODO: Add validation support.
		entry = ttk.Entry(self)
		entry.grid(row = row, column = 2, sticky = 'e')
		entry.bind(event, wrapper)
		entry.insert(0, default)

		# Create a get function.
		def get():
			try:
				return result(entry.get())
			except:
				entry.delete(0, 'end')
				raise InvalidOption(name, entry.get())

		# Add the option to the options array.
		self.options[name] = (entry, get)

	def add_combobox_option(self, name, options, default):
		""" Add a combobox option """

		row = self.grid_size()[1]

		# Create a label.
		label = ttk.Label(self, text = name + ':')
		label.grid(row = row, column = 1, sticky = 'w')

		# Create the combobox.
		box = ttk.Combobox(self)
		box.grid(row = row, column = 2, sticky = 'e')
		box['values'] = list(options)

		# Set the default.
		box.current(options.index(default))

		# Add the option to the options array.
		self.options[name] = (box, lambda: box.get())

	def add_file_option(self, name, default):
		""" Add a file selection option """

		row = self.grid_size()[1]

		# Create a string variable.
		file = tk.StringVar()

		# Create a label.
		label = ttk.Label(self)
		label.grid(row = row, column = 1, columnspan = 2, \
			sticky = 'w')

		# Create a callback for changing the label.
		def label_callback(var_name, index, operation):
			label.config(text = "{}: {}".format(name, file.get()))
		# Add the callback.
		file.trace('w', label_callback)
		# Set the default.
		file.set(default)

		# Create a button to change the file.
		button = ttk.Button(self, text = "Change", \
			command = lambda: file.set(tkFileDialog.askopenfilename()))
		button.grid(row = row, column = 3)

		# Add the option to the options array.
		self.options[name] = (file, lambda: file.get())

	def __iter__(self):
		""" Iterate through the existing options """

		for name, (entry, get) in self.options.items():
			yield (name, get())
		raise StopIteration

	def get(self, name):
		""" Get the value of the given variable """

		return self.options[name][1]()

class ItemList(ttk.Frame):
	""" An itemlist with flexible per-item options """

	def __init__(self, master, name, function, *args, **kargs):
		""" Initialise self """

		# Init self.
		ttk.Frame.__init__(self, master, *args, **kargs)

		# Add the label.
		labelframe = ttk.Frame(self)
		labelframe.grid(row = 1, column = 1, sticky = 'nw')
		label = ttk.Label(labelframe, text = name)
		label.grid(row = 1, sticky = 'nw')

		# Add the listbox.
		# Create the frame holding both.
		listframe = ttk.Frame(self)
		listframe.grid(row = 1, column = 2, rowspan = 3, sticky = 'nes')
		# Create the scrollbar and listbox.
		scroll = ttk.Scrollbar(listframe, orient = 'vertical')
		box = tk.Listbox(listframe, selectmode = 'extended', \
			exportselection = False, yscrollcommand = scroll.set)
		scroll.config(command = box.yview)
		scroll.pack(side = 'right', fill = 'y', expand = True)
		box.pack(side = 'left', fill = 'both', expand = True)

		# Items.
		self.items = {} # name: {key: value}

		# Create callbacks for selection changes, to change the
		# context-dependent options.
		context = {'frame': None, 'cleanup': {}}
		def change_active(event):

			def remove_old(new_active):
				""" Save the contents, and remove the old frame """
				frame = context['frame']
				if frame != None:
					# Save the contents.

					# Destroy the old frame.
					frame.grid_forget()
					frame.destroy()
					context['frame'] = None

			# Remove and remake as required.
			# We cannot use get('active') because active appears to lag behind
			# the current selection :(
			if len(box.curselection()) > 0:
				index = box.curselection()[0]
				active = box.get(index)

				remove_old(active)
				# We actually create an 'Options' frame.
				frame = context['frame'] = Options(self)

				# Add a rename option.
				# Create the callback.
				def rename_item(name, index):
					""" Rename an existing item """

					if len(name) > 0 and name not in self.items:
						box.insert(index + 1, name)
						# Update selection, if required.
						if index in box.curselection():
							box.selection_set(index + 1)
						box.delete(index)

				frame.add_raw_option("Name", lambda n: rename_item(n, index), \
					active)

				# Add the custom buttons.
				function(frame, active)

				# Add a 'delete' button.
				# Create the callback.
				def delete_item():
					""" Delete the currently selected items """
					selected = box.curselection()
					while len(selected) != 0:
						item = box.get(selected[0])
						del(self.items[item])
						box.delete(selected[0])
						selected = box.curselection()
					remove_old(None) # Nothing left selected...
				# Create a delete button.
				delete_button = ttk.Button(frame, text = "Delete", \
					command = delete_item)
				delete_button.grid(row = frame.grid_size()[1], column = 1, \
					sticky = 'w')

				# Grid the frame in.
				frame.grid(row = 3, column = 1, sticky = 'sw')

		# Bind select events to updating the active element.
		box.bind("<<ListboxSelect>>", change_active)

		# Add a 'new' button.
		def add_new():
			""" Add a new item to the listbox """
			# Find a unique name
			id = 0
			name = 'new'
			while name in self.items:
				id += 1
				name = 'new-' + str(id)
			# Add the item.
			self.items[name] = {}
			box.insert('end', name)
			# Clear the existing selection.
			for selected in box.curselection():
				box.selection_clear(selected)
			# Set the selection to the new item.
			box.selection_set(first = 'end')
			# Activate the new item.
			box.activate('end')
			# 'see' the new item.
			box.see('end')
			# Update the active element.
			change_active("add")
		# Create a new button.
		button = ttk.Button(labelframe, text = 'New', command = add_new)
		button.grid(row = 2, sticky = 'nw')

class Main(ttk.Frame):
	""" The main window """

	def __init__(self, master, *args, **kargs):
		""" Init self """

		self.master = master
		ttk.Frame.__init__(self, self.master, *args, **kargs)

		# Pack self.
		self.pack(expand=True, fill='both')

		# Models.
		self.models = []
		# Values.
		self.values = []

		# Create the widgets...

		# Run buttons.
		lower = ttk.Frame(self)
		lower.pack(side='bottom', fill='x')
		preview = ttk.Button(lower, text='Preview', command=self.preview)
		preview.pack(side='left')
		render = ttk.Button(lower, text='Render', command=self.render)
		render.pack(side='right')

		# Create the options...
		self.options = Options(self)
		self.options.pack(expand=True, fill='both')
		# Add the 'raw' (string) options.
		self.options.add_raw_option("Title", lambda x: x, "")
		def check_int(i, min, max):
			if min <= int(i) <= max:
				return int(i)
			else:
				raise ValueError("{} not within [{}, {}]!".format(i, min, max))
		self.options.add_raw_option("FPS", \
			lambda x: check_int(x, MIN_FPS, MAX_FPS), 4)
		def check_size(size):
			x, y = size.split('x')
			return int(x), int(y)
		self.options.add_raw_option("Movie dimensions", check_size, \
			"1280x1024")
		self.options.add_raw_option("Text size", \
			lambda x: check_int(x, MIN_TEXT_HEIGHT, MAX_TEXT_HEIGHT), 30)
		# Add the listbox options.
		self.options.add_combobox_option("Value transform", \
			transforms.transformations.keys(), 'basic')
		self.options.add_combobox_option("Timewarp", \
			transforms.times.keys(), 'basic')
		self.options.add_combobox_option("Edge render", ["True", "False"], \
			"True")
		# Add the file options.
		self.options.add_file_option("GIS files", \
			"H:/My Documents/vis/gis/SmallPatches")
		self.options.add_file_option("CSV directory", \
			"H:/My Documents/vis/csv/small")
		self.options.add_file_option("Movie filename", \
			"H:/My Documents/vis/movies/movie.mp4")

		# Add the Models object list.
		def model_options(master, active):
			""" Create the additional options for a specified value """

			master.add_file_option("GIS files", \
				"H:/My Documents/vis/gis/SmallPatches")
			master.add_file_option("CSV directory", \
				"H:/My Documents/vis/csv/small")
			# TODO: Having a deletion button should be controlled by whether or
			# 		not some value is using this particular model.

		model_list = ItemList(self, "Models", model_options)
		model_list.pack(expand = True, fill = 'both')

			# Add the Values object list.
		def value_options(master, active):
			""" Create the additional options for a specified value """

			# TODO: This should update when the one in model_list does.
			models = list(model_list.items.keys())
			if len(models) > 0:
				master.add_combobox_option("Model", model_list.items.keys(), models[0])
				master.add_combobox_option("Value transform", \
					transforms.transformations.keys(), 'basic')
				# TODO: Figure this out from the model.
				# TODO: This should update when the model is changed.
				master.add_combobox_option("Field", ["Soil.SoilWater.Drainage"], \
					"Soil.SoilWater.Drainage")

		value_list = ItemList(self, "Values", value_options)
		value_list.pack(expand = True, fill = 'both')

	def gen_frames(self):
		""" Return a render_frame function and frames variable """

		#TODO: We should allow more than one value...
		#TODO: This should be done elsewhere.
		# Create a Model.
		model = Model(self.options.get('GIS files'), self.options.get('CSV directory'))
		# Create the values.
		self.values = [Values(model, "Soil.SoilWater.Drainage")]

		# Create the frame rendering function.
		return gen_render_frame(self.values, self.options.get('Text size'), \
			self.options.get('Title'), self.options.get('Timewarp'), \
			self.options.get('Edge render') == "True")

	def preview(self):
		""" Preview! """

		# Generate the render_frame function and the frame count.
		render_frame, frames = self.gen_frames()

		# Play the animation.
		#TODO: This should be launched in another thread to avoid hanging tk?
		preview(render_frame, frames, self.options.get('FPS'), \
			self.options.get('Movie dimensions'), self.options.get('Title'))

	def render(self):
		""" Render! """

		# Generate the render_frame function and the frame count.
		render_frame, frames = self.gen_frames()

		# Play the animation.
		#TODO: This should be launched in another thread to avoid hanging tk?
		#TODO: Progress bar?
		render(render_frame, frames, self.options.get('FPS'), \
			self.options.get('Movie dimensions'), self.options.get('Movie filename'))

if __name__ == "__main__":
	root = tk.Tk()
	ui = Main(root, width=600, height=600)
	ui.mainloop()
	<commit_msg>Refactoring...<commit_after>""" User interface code for the animation renderer.

	Author: Alastair Hughes
"""

# Local imports.
from display import preview, render
from animate import gen_render_frame
from models import Model, Values
from constants import MAX_FPS, MIN_FPS, MAX_TEXT_HEIGHT, MIN_TEXT_HEIGHT
import transforms

# Tkinter imports
import Tkinter as tk
import tkFileDialog
import ttk

class InvalidOption(ValueError):
	""" Error to be raised if an option is invalid """

	def __init__(self, name, value):
		ValueError.__init__(self, \
			"Option '{}' has an invalid value of '{}'!".format(name, value))

class UI(ttk.Frame):

	def __init__(self, master, pages, *args, **kargs):
		""" Initialise self """

		self.master = master
		ttk.Frame.__init__(self, self.master, *args, **kargs)
		self.pack(expand=True, fill='both')

		# Create the tabs.
		self.windows = ttk.Notebook(self)
		for page in pages:
			self.windows.add(pages[page](self.windows), text=page)
		self.windows.pack(expand=True, fill='both')

class Options(ttk.Frame):

	def __init__(self, master):
		""" Init self """

		self.master = master
		ttk.Frame.__init__(self, self.master)

		# Options added.
		self.options = {} # name: (entry, get)

	def add_raw_option(self, name, result, default, event = "<Return>"):
		""" Add an option """

		row = self.grid_size()[1]

		# Create a wrapper callback.
		def wrapper(event):
			try:
				result(event.widget.get())
			except:
				event.widget.delete(0, 'end')

		# Create a label.
		label = ttk.Label(self, text = name + ':')
		label.grid(row = row, column = 1, sticky = 'w')

		# Create an entry.
		#TODO: Add validation support.
		entry = ttk.Entry(self)
		entry.grid(row = row, column = 2, sticky = 'e')
		entry.bind(event, wrapper)
		entry.insert(0, default)

		# Create a get function.
		def get():
			try:
				return result(entry.get())
			except:
				entry.delete(0, 'end')
				raise InvalidOption(name, entry.get())

		# Add the option to the options array.
		self.options[name] = (entry, get)

	def add_combobox_option(self, name, options, default):
		""" Add a combobox option """

		row = self.grid_size()[1]

		# Create a label.
		label = ttk.Label(self, text = name + ':')
		label.grid(row = row, column = 1, sticky = 'w')

		# Create the combobox.
		box = ttk.Combobox(self)
		box.grid(row = row, column = 2, sticky = 'e')
		box['values'] = list(options)

		# Set the default.
		box.current(options.index(default))

		# Add the option to the options array.
		self.options[name] = (box, lambda: box.get())

	def add_file_option(self, name, default):
		""" Add a file selection option """

		row = self.grid_size()[1]

		# Create a string variable.
		file = tk.StringVar()

		# Create a label.
		label = ttk.Label(self)
		label.grid(row = row, column = 1, columnspan = 2, \
			sticky = 'w')

		# Create a callback for changing the label.
		def label_callback(var_name, index, operation):
			label.config(text = "{}: {}".format(name, file.get()))
		# Add the callback.
		file.trace('w', label_callback)
		# Set the default.
		file.set(default)

		# Create a button to change the file.
		button = ttk.Button(self, text = "Change", \
			command = lambda: file.set(tkFileDialog.askopenfilename()))
		button.grid(row = row, column = 3)

		# Add the option to the options array.
		self.options[name] = (file, lambda: file.get())

	def __iter__(self):
		""" Iterate through the existing options """

		for name, (entry, get) in self.options.items():
			yield (name, get())
		raise StopIteration

	def get(self, name):
		""" Get the value of the given variable """

		return self.options[name][1]()

class ScrolledListbox(ttk.Frame):
	""" Scrolled listbox """

	def __init__(self, master, *args, **kargs):
		""" Initialise self """

		ttk.Frame.__init__(self, master, *args, **kargs)

		# Create the scrollbar and listbox.
		scroll = ttk.Scrollbar(self, orient = 'vertical')
		box = tk.Listbox(self, selectmode = 'extended', \
			exportselection = False, yscrollcommand = scroll.set)
		scroll.config(command = box.yview)
		scroll.pack(side = 'right', fill = 'y', expand = True)
		box.pack(side = 'left', fill = 'both', expand = True)

		# Pass-through the listbox functions.
		self.curselection = lambda *args: box.curselection(*args)
		self.get = lambda *args: box.get(*args)
		self.delete = lambda *args: box.delete(*args)
		self.selection_set = \
			lambda *args, **kargs: box.selection_set(*args, **kargs)
		self.selection_clear = \
			lambda *args, **kargs: box.selection_clear(*args, **kargs)
		self.bind = lambda *args: box.bind(*args)
		self.see = lambda *args: box.see(*args)
		self.insert = lambda *args: box.insert(*args)

class ItemList(ttk.Frame):
	""" An itemlist with flexible per-item options """

	def __init__(self, master, name, function, *args, **kargs):
		""" Initialise self """

		# Init self.
		ttk.Frame.__init__(self, master, *args, **kargs)
		self.name = name
		self.function = function
		# Map for items in the list.
		self.items = {} # name: {key: value}
		# Current context frame.
		self.context = None

		# Create the widgets.
		self.create_widgets()

	def create_widgets(self):

		# Add the label.
		labelframe = ttk.Frame(self)
		labelframe.grid(row = 1, column = 1, sticky = 'nw')
		label = ttk.Label(labelframe, text = self.name)
		label.grid(row = 1, sticky = 'nw')

		self.box = ScrolledListbox(self)
		self.box.grid(row = 1, column = 2, rowspan = 3, sticky = 'nes')

		# Bind select events to updating the active element.
		self.box.bind("<<ListboxSelect>>", self.change_active)

		# Add a 'new' button.
		# Create a new button.
		button = ttk.Button(labelframe, text = 'New', command = self.add_new)
		button.grid(row = 2, sticky = 'nw')

	def add_new():
		""" Add a new item to the listbox """

		# Find a unique name
		id = 0
		name = 'new'
		while name in self.items:
			id += 1
			name = 'new-' + str(id)

		# Add the item.
		self.items[name] = {}
		self.box.insert('end', name)

		# Clear the existing selection.
		for selected in self.box.curselection():
			self.box.selection_clear(selected)

		# Set the selection to the new item.
		self.box.selection_set(first = 'end')

		# 'see' the new item.
		self.box.see('end')

		# Update the active element.
		self.change_active("add")

	def change_active(self, event):
		""" Change the active item """

		def remove_old(new_active):
			""" Save the contents, and remove the old frame """
			if self.context != None:
				# Save the contents.

				# Destroy the old frame.
				self.context.grid_forget()
				self.context.destroy()
				self.context = None

		# Remove and remake as required.
		# We cannot use get('active') because active appears to lag behind
		# the current selection :(
		if len(self.box.curselection()) > 0:
			index = self.box.curselection()[0]
			active = self.box.get(index)

			remove_old(active)
			# We actually create an 'Options' frame.
			self.context = Options(self)

			# Add a rename option.
			# Create the callback.
			def rename_item(name, index):
				""" Rename an existing item """

				if len(name) > 0 and name not in self.items:
					self.box.insert(index + 1, name)
					# Update selection, if required.
					if index in self.box.curselection():
						self.box.selection_set(index + 1)
					self.box.delete(index)

			self.context.add_raw_option("Name", lambda n: rename_item(n, index), \
				active)

			# Add the custom buttons.
			self.function(self.context, active)

			# Add a 'delete' button.
			# Create the callback.
			def delete_item():
				""" Delete the currently selected items """
				selected = self.box.curselection()
				while len(selected) != 0:
					item = self.box.get(selected[0])
					del(self.items[item])
					self.box.delete(selected[0])
					selected = self.box.curselection()
				remove_old(None) # Nothing left selected...
			# Create a delete button.
			delete_button = ttk.Button(self.context, text = "Delete", \
				command = delete_item)
			delete_button.grid(row = self.context.grid_size()[1], column = 1, \
				sticky = 'w')

			# Grid the frame in.
			self.context.grid(row = 3, column = 1, sticky = 'sw')

class Main(ttk.Frame):
	""" The main window """

	def __init__(self, master, *args, **kargs):
		""" Init self """

		self.master = master
		ttk.Frame.__init__(self, self.master, *args, **kargs)

		# Pack self.
		self.pack(expand=True, fill='both')

		# Models.
		self.models = []
		# Values.
		self.values = []

		# Create the widgets...

		# Run buttons.
		lower = ttk.Frame(self)
		lower.pack(side='bottom', fill='x')
		preview = ttk.Button(lower, text='Preview', command=self.preview)
		preview.pack(side='left')
		render = ttk.Button(lower, text='Render', command=self.render)
		render.pack(side='right')

		# Create the options...
		self.options = Options(self)
		self.options.pack(expand=True, fill='both')
		# Add the 'raw' (string) options.
		self.options.add_raw_option("Title", lambda x: x, "")
		def check_int(i, min, max):
			if min <= int(i) <= max:
				return int(i)
			else:
				raise ValueError("{} not within [{}, {}]!".format(i, min, max))
		self.options.add_raw_option("FPS", \
			lambda x: check_int(x, MIN_FPS, MAX_FPS), 4)
		def check_size(size):
			x, y = size.split('x')
			return int(x), int(y)
		self.options.add_raw_option("Movie dimensions", check_size, \
			"1280x1024")
		self.options.add_raw_option("Text size", \
			lambda x: check_int(x, MIN_TEXT_HEIGHT, MAX_TEXT_HEIGHT), 30)
		# Add the listbox options.
		self.options.add_combobox_option("Value transform", \
			transforms.transformations.keys(), 'basic')
		self.options.add_combobox_option("Timewarp", \
			transforms.times.keys(), 'basic')
		self.options.add_combobox_option("Edge render", ["True", "False"], \
			"True")
		# Add the file options.
		self.options.add_file_option("GIS files", \
			"H:/My Documents/vis/gis/SmallPatches")
		self.options.add_file_option("CSV directory", \
			"H:/My Documents/vis/csv/small")
		self.options.add_file_option("Movie filename", \
			"H:/My Documents/vis/movies/movie.mp4")

		# Add the Models object list.
		def model_options(master, active):
			""" Create the additional options for a specified value """

			master.add_file_option("GIS files", \
				"H:/My Documents/vis/gis/SmallPatches")
			master.add_file_option("CSV directory", \
				"H:/My Documents/vis/csv/small")
			# TODO: Having a deletion button should be controlled by whether or
			# 		not some value is using this particular model.

		model_list = ItemList(self, "Models", model_options)
		model_list.pack(expand = True, fill = 'both')

			# Add the Values object list.
		def value_options(master, active):
			""" Create the additional options for a specified value """

			# TODO: This should update when the one in model_list does.
			models = list(model_list.items.keys())
			if len(models) > 0:
				master.add_combobox_option("Model", model_list.items.keys(), models[0])
				master.add_combobox_option("Value transform", \
					transforms.transformations.keys(), 'basic')
				# TODO: Figure this out from the model.
				# TODO: This should update when the model is changed.
				master.add_combobox_option("Field", ["Soil.SoilWater.Drainage"], \
					"Soil.SoilWater.Drainage")

		value_list = ItemList(self, "Values", value_options)
		value_list.pack(expand = True, fill = 'both')

	def gen_frames(self):
		""" Return a render_frame function and frames variable """

		#TODO: We should allow more than one value...
		#TODO: This should be done elsewhere.
		# Create a Model.
		model = Model(self.options.get('GIS files'), self.options.get('CSV directory'))
		# Create the values.
		self.values = [Values(model, "Soil.SoilWater.Drainage")]

		# Create the frame rendering function.
		return gen_render_frame(self.values, self.options.get('Text size'), \
			self.options.get('Title'), self.options.get('Timewarp'), \
			self.options.get('Edge render') == "True")

	def preview(self):
		""" Preview! """

		# Generate the render_frame function and the frame count.
		render_frame, frames = self.gen_frames()

		# Play the animation.
		#TODO: This should be launched in another thread to avoid hanging tk?
		preview(render_frame, frames, self.options.get('FPS'), \
			self.options.get('Movie dimensions'), self.options.get('Title'))

	def render(self):
		""" Render! """

		# Generate the render_frame function and the frame count.
		render_frame, frames = self.gen_frames()

		# Play the animation.
		#TODO: This should be launched in another thread to avoid hanging tk?
		#TODO: Progress bar?
		render(render_frame, frames, self.options.get('FPS'), \
			self.options.get('Movie dimensions'), self.options.get('Movie filename'))

if __name__ == "__main__":
	root = tk.Tk()
	ui = Main(root, width=600, height=600)
	ui.mainloop()
	<|endoftext|>


<commit_before>#!/usr/bin/python
import sys
from lxml import etree
import urllib
import gzip
import io
import time
import cmd

# TODO
# Some systems have more than one name
# Binaries don't work well.
# Alpha Centauri (not circumbinary)
# Kepler-47 (circumbinary)
# There's something weird with the system name autocomplete

try:
    from enum import Enum
except ImportError:
    print "You need the enum backport."
    print "pip install enum34"

try:
    from colorama import Fore, Back, Style, init
    init()
except ImportError:
    # colorama support is optional.
    pass

class PlanetSize(Enum):
    """Defines fuzzy planet size labels.

    The enum value is an ASCII depiction."""
    terrestrial = '.'
    neptune = 'o'
    jupiter = '@'
    unknown = '?'

#Constants
JUPITER_IN_EARTH_MASSES = 317.828
NEPTUNE_IN_EARTH_MASSES = 17.147
JUPITER_IN_EARTH_RADII = 11.209 #equatorial
NEPTUNE_IN_EARTH_RADII = 3.883 #equatorial

SYMBOL = {'sun': u'\u2609',
          'earth': u'\u2295',
          'neptune': u'\u2646',
          'jupiter': u'\u2643'}

if not sys.modules.has_key('colorama'):
    # Doesn't wreck things if colorama isn't installed.
    STARCOLOR = {'O': '',
                 'B': '',
                 'A': '',
                 'F': '',
                 'G': '',
                 'K': '',
                 'M': ''}
    RSTCOLOR = ''
else:
    STARCOLOR = {'O': Fore.MAGENTA + Style.BRIGHT,
                 'B': Fore.BLUE + Style.BRIGHT,
                 'A': Fore.WHITE + Style.BRIGHT,
                 'F': Fore.YELLOW + Style.BRIGHT,
                 'G': Fore.YELLOW,
                 'K': Fore.RED + Back.YELLOW,
                 'M': Fore.RED}
    RSTCOLOR = Fore.RESET + Style.RESET_ALL

def demo():
    # Output mass and radius of all planets
    for planet in oec.findall(".//planet"):
        print [planet.findtext("mass"), planet.findtext("radius")]

    # Find all circumbinary planets
    for planet in oec.findall(".//binary/planet"):
        print planet.findtext("name")

    # Output distance to planetary system (in pc, if known) and number of planets in system
    for system in oec.findall(".//system"):
        print system.findtext("distance"), len(system.findall(".//planet"))

def get_planet_radius(planet, multiple='jupiter'):
    """Returns planet radius as a float times Jupiter Radii

    :param planet: lxml etree node for <planet>
    :param multiple: String, either 'jupiter', 'neptune', or 'earth'. \
    If earth, will return number of earth masses, etc.
    """
    radius_node = planet.find('radius')
    if radius_node is None:
        return None
    jup_radii = float(radius_node.text)
    if multiple == 'jupiter':
        return jup_radii
    elif multiple == 'neptune':
        return (jup_radii * JUPITER_IN_EARTH_RADII) / NEPTUNE_IN_EARTH_RADII
    elif multiple == 'earth':
        return jup_radii * JUPITER_IN_EARTH_RADII

def get_planet_mass(planet, multiple='jupiter'):
    """Returns planet mass as a float times mass of multiple.

    :param planet: lxml etree node for <planet>
    :param multiple: String, either 'jupiter', 'neptune', or 'earth'. \
    If earth, will return number of earth masses, etc.
    """
    jup_mass = float(planet.find('mass').text)
    if multiple == 'jupiter':
        return jup_mass
    elif multiple == 'neptune':
        return (jup_mass * JUPITER_IN_EARTH_MASSES) / NEPTUNE_IN_EARTH_MASSES
    elif multiple == 'earth':
        return jup_mass * JUPITER_IN_EARTH_MASSES

def get_planet_size(planet):
    """Returns PlanetSize enum for planet.

    Makes determination based on radius and mass criteria.
    :param planet: lxml etree node for <planet>
    """
    earth_radii = get_planet_radius(planet, 'earth')
    if earth_radii is not None:
        if earth_radii < 2:
            return PlanetSize.terrestrial
        elif earth_radii > 7:
            return PlanetSize.jupiter
        else:
            return PlanetSize.neptune
    else:
        earth_masses = get_planet_mass(planet, 'earth')
        if earth_masses < 10:
            return PlanetSize.terrestrial
        elif earth_masses > 30:
            return PlanetSize.jupiter
        else:
            return PlanetSize.neptune
    return PlanetSize.unknown

def get_etree():
    url = "https://github.com/OpenExoplanetCatalogue/oec_gzip/raw/master/systems.xml.gz"
    fo = io.BytesIO(urllib.urlopen(url).read())
    tree = etree.parse(gzip.GzipFile(fileobj=fo))
    return tree

def find_system_by_name(name):
    """Returns system node matching supplied name

    :param name: string containing desired system name.
    """
    systems = tree.findall('.//system')
    names = [system.find('name').text for system in systems]
    return systems[names.index(name.replace('_',' '))]

def most_recent_planet(tree):
    """Returns planet node that has most recent update date.

    :param tree: lxml etree
    :returns: lxml etree
    """
    updates = tree.findall('.//lastupdate')
    update_time = [time.strptime(date.text,'%y/%m/%d') for date in updates]
    planet = updates[update_time.index(max(update_time))].getparent()
    return planet

def most_recent_system(tree):
    """Returns system with most recently updated planet.

    :param tree: lxml etree
    """
    planet = most_recent_planet(tree)
    return get_parent_tag(planet, 'system')

def get_parent_tag(node, tag):
    """Traverses up the tree from node until it finds tag and then returns that node.

    :param node: The child node to start the search from
    :param tag: String with tag type we're searching for.
    """
    if node.tag == tag:
        return node
    else:
        return get_parent_tag(node.getparent(), tag)

def num_planets(tree):
    """Returns the total number of planets in the tree

    :param tree: lxml etree
    :returns: integer representing number of planets.
    """
    return int(tree.xpath("count(.//planet)"))

def num_stars(tree):
    """Returns the total number of stars in the tree

    :param tree: lxml etree
    :returns: integer representing number of planets.
    """
    return int(tree.xpath("count(.//star)"))

def num_systems(tree):
    """Returns the total number of systems in the tree

    :param tree: lxml etree
    :returns: integer representing number of planets.
    """
    return int(tree.xpath("count(.//system)"))

def largest_system(tree):
    """Returns system node that has the largest number of planets.

    :param tree: lxml etree
    :returns: lxml etree centered on system with the mode planets.
    """
    system_size = [num_planets(system) for system in tree.xpath(".//system")]
    largest_idx = system_size.index(max(system_size))
    root = tree.xpath("//systems")[0]
    return root[largest_idx]

def write_tree(tree,fn):
    xmlstr = etree.tostring(tree, pretty_print=True)
    with open(fn, 'w') as f:
        f.write(xmlstr)

def planet_name(planet):
    """Returns name of planet, just letter.
    """
    return planet.find('name').text.split(' ')[-1]

def spectral_name(star):
    """Retruns string containing spectral name of star, with colorama color

    Doesn't include ANSII code if colorama is not installed.
    :param star: lxml etree on <star> node
    """
    spectral = star.find('spectraltype').text
    stellarclass = spectral[0]
    if stellarclass in STARCOLOR.keys():
        return STARCOLOR[stellarclass] + spectral + RSTCOLOR
    else:
        return spectral

def summarize_star(star):
    """return one line summary of star"""
    if star.find('name').text[-2] == ' ':
        name = star.find('name').text[-1]
    else:
        name = ' '
    return u'{0} {1} {2}{3}'.format(name, spectral_name(star), star.find('mass').text, 'M'+SYMBOL['sun'])

def format_planet_mass_str(planet):
    """Takes float multiple of Jupiter mass, and returns it rounded and prefixed.

    Uses J2.3 for jupiter masses.
    Uses E3.4 for earth masses if under 0.05 jupiter masses.
    """
    jup_mass = get_planet_mass(planet, 'jupiter')
    planet_size = get_planet_size(planet)
    flt_mass = float(planet.find('mass').text)
    if planet_size.name == 'terrestrial':
        use_mass = get_planet_mass(planet, 'earth')
        symbol = SYMBOL['earth']
    else:
        use_mass = jup_mass
        symbol = SYMBOL['jupiter']
    return u'{:0.3f}M{}'.format(round(use_mass, 3), symbol)

def summarize_planet(planet):
    """Return one line summary of planet"""
    if planet.find('list').text == "Confirmed planets":
        reliable = ' '
    else:
        reliable = '?'

    letter = planet_name(planet)

    mass = format_planet_mass_str(planet)
    return u'{0} {1} {2}'.format(reliable, letter, mass)

def summarize_system(system):
    """Prints concise summary of system represented by tree

    :param system: lxml etree based on <system> tag.
    """
    s = []
    s.append(system.find('name').text + ' - ' + str(num_stars(system)) + ' stars - ' + str(num_planets(system)) + ' planets')
    s.append(ascii_system(system))
    binary = system.find('binary')
    if binary is not None:
        system = binary
    for star in system.iterfind('star'):
        s.append(' ' + summarize_star(star))
        for planet in star.iterfind('planet'):
            s.append('   ' + summarize_planet(planet))
    for planet in system.iterfind('planet'):
        print "circumbinary"
    return '\n'.join(s)

def get_max_sma(tree):
    """Returns max Semi-Major Axis (AU) for all planets in tree.

    :param tree: An lxml etree
    """
    allsmas = [float(x.text) for x in tree.findall('.//semimajoraxis')]
    return max(allsmas)

def ascii_system(system):
    """Return an ASCII graphic of the system.

    :param system: lxml etree based on <system> tag.
    """
    dots = []
    names = []
    maxsma = get_max_sma(system)
    for star in system.iterfind('star'):
        t = [' ']*80
        d = [' ']*80
        d[0] = '*'
        for planet in star.iterfind('planet'):
            sma = float(planet.find('semimajoraxis').text)
            loc = int((sma / maxsma) * 78) + 1
            while t[loc] != ' ':
                loc += 1
            t[loc] = planet_name(planet)
            d[loc] = get_planet_size(planet).value
        names.append(''.join(t))
        dots.append(''.join(d))
    result = []
    for idx in range(len(dots)):
        result.append(dots[idx])
        result.append(names[idx])
        result.append('')
    return '\n'.join(result)

def tweet_system(system):
    """Return system summary suitable for a twitter message.

    :param system: lxml etree based on <system> tag.
    """
    s = []
    for star in system.iterfind('star'):
        s.append(star.find('name').text)
        for planet in star.iterfind('planet'):
            planet_size = get_planet_size(planet)
            s.append('  ' + planet_size.value + ' ' + planet_name(planet))
    return '\n'.join(s)

def get_system_names(tree):
    """Returns list of all system names.

    :param tree: Tree to find systems in.
    """
    names = [n.replace(' ','_') for n in tree.xpath('./system/name/text()')]
    return names

class PlanetCmd(cmd.Cmd):
    def __init__(self, system_names):
        cmd.Cmd.__init__(self)
        self.prompt = '> '
        self.intro = "Exoplanet Explorer (type help commands):"
        self.system_names = system_names

    def do_most_recent_planet(self, args):
        planet = most_recent_planet(tree)
        print "Most recently updated planet is " + str(planet.find('name').text)
        print "  updated on " + planet.find('lastupdate').text
        print ""
        print planet.find('description').text

    def do_most_recent_system(self, args):
        print "Most recently updated system is:"
        most_recent = most_recent_system(tree)
        print summarize_system(most_recent)

    def do_stats(self, args):
        print "Catalog contains " + str(num_planets(tree)) + " planets in " + str(num_systems(tree)) + " systems."

    def do_largest_system(self, args):
        print "Largest system is:"
        largest = largest_system(tree)
        #write_tree(largest,'largest.xml')
        print summarize_system(largest)

    def do_system(self, system_name):
        print summarize_system(find_system_by_name(system_name))

    def do_tweet(self, system_name):
        print tweet_system(find_system_by_name(system_name))

    def complete_tweet(self, text, line, begidx, endidx):
        return self.complete_system(text, line, begidx, endidx)

    def complete_system(self, text, line, begidx, endidx):
        if not text:
            completions = self.system_names[:]
        else:
            completions = [ f
                            for f in self.system_names
                            if f.startswith(text)
                            ]
        return completions

    def do_exit(self, args):
        exit()

if __name__ == '__main__':
    tree = get_etree()
    system_names = get_system_names(tree)
    PlanetCmd(system_names).cmdloop()

<commit_msg>ascii art is now displaing for alpha centauri type binaries, but it needs some work.<commit_after>#!/usr/bin/python
import sys
from lxml import etree
import urllib
import gzip
import io
import time
import cmd

# TODO
# Some systems have more than one name
# Binaries don't work well.
# Alpha Centauri (not circumbinary)
# Kepler-47 (circumbinary)
# There's something weird with the system name autocomplete

try:
    from enum import Enum
except ImportError:
    print "You need the enum backport."
    print "pip install enum34"

try:
    from colorama import Fore, Back, Style, init
    init()
except ImportError:
    # colorama support is optional.
    pass

class PlanetSize(Enum):
    """Defines fuzzy planet size labels.

    The enum value is an ASCII depiction."""
    terrestrial = '.'
    neptune = 'o'
    jupiter = '@'
    unknown = '?'

#Constants
JUPITER_IN_EARTH_MASSES = 317.828
NEPTUNE_IN_EARTH_MASSES = 17.147
JUPITER_IN_EARTH_RADII = 11.209 #equatorial
NEPTUNE_IN_EARTH_RADII = 3.883 #equatorial

SYMBOL = {'sun': u'\u2609',
          'earth': u'\u2295',
          'neptune': u'\u2646',
          'jupiter': u'\u2643'}

if not sys.modules.has_key('colorama'):
    # Doesn't wreck things if colorama isn't installed.
    STARCOLOR = {'O': '',
                 'B': '',
                 'A': '',
                 'F': '',
                 'G': '',
                 'K': '',
                 'M': ''}
    RSTCOLOR = ''
else:
    STARCOLOR = {'O': Fore.MAGENTA + Style.BRIGHT,
                 'B': Fore.BLUE + Style.BRIGHT,
                 'A': Fore.WHITE + Style.BRIGHT,
                 'F': Fore.YELLOW + Style.BRIGHT,
                 'G': Fore.YELLOW,
                 'K': Fore.RED + Back.YELLOW,
                 'M': Fore.RED}
    RSTCOLOR = Fore.RESET + Style.RESET_ALL

def demo():
    # Output mass and radius of all planets
    for planet in oec.findall(".//planet"):
        print [planet.findtext("mass"), planet.findtext("radius")]

    # Find all circumbinary planets
    for planet in oec.findall(".//binary/planet"):
        print planet.findtext("name")

    # Output distance to planetary system (in pc, if known) and number of planets in system
    for system in oec.findall(".//system"):
        print system.findtext("distance"), len(system.findall(".//planet"))

def get_planet_radius(planet, multiple='jupiter'):
    """Returns planet radius as a float times Jupiter Radii

    :param planet: lxml etree node for <planet>
    :param multiple: String, either 'jupiter', 'neptune', or 'earth'. \
    If earth, will return number of earth masses, etc.
    """
    radius_node = planet.find('radius')
    if radius_node is None:
        return None
    jup_radii = float(radius_node.text)
    if multiple == 'jupiter':
        return jup_radii
    elif multiple == 'neptune':
        return (jup_radii * JUPITER_IN_EARTH_RADII) / NEPTUNE_IN_EARTH_RADII
    elif multiple == 'earth':
        return jup_radii * JUPITER_IN_EARTH_RADII

def get_planet_mass(planet, multiple='jupiter'):
    """Returns planet mass as a float times mass of multiple.

    :param planet: lxml etree node for <planet>
    :param multiple: String, either 'jupiter', 'neptune', or 'earth'. \
    If earth, will return number of earth masses, etc.
    """
    jup_mass = float(planet.find('mass').text)
    if multiple == 'jupiter':
        return jup_mass
    elif multiple == 'neptune':
        return (jup_mass * JUPITER_IN_EARTH_MASSES) / NEPTUNE_IN_EARTH_MASSES
    elif multiple == 'earth':
        return jup_mass * JUPITER_IN_EARTH_MASSES

def get_planet_size(planet):
    """Returns PlanetSize enum for planet.

    Makes determination based on radius and mass criteria.
    :param planet: lxml etree node for <planet>
    """
    earth_radii = get_planet_radius(planet, 'earth')
    if earth_radii is not None:
        if earth_radii < 2:
            return PlanetSize.terrestrial
        elif earth_radii > 7:
            return PlanetSize.jupiter
        else:
            return PlanetSize.neptune
    else:
        earth_masses = get_planet_mass(planet, 'earth')
        if earth_masses < 10:
            return PlanetSize.terrestrial
        elif earth_masses > 30:
            return PlanetSize.jupiter
        else:
            return PlanetSize.neptune
    return PlanetSize.unknown

def get_etree():
    url = "https://github.com/OpenExoplanetCatalogue/oec_gzip/raw/master/systems.xml.gz"
    fo = io.BytesIO(urllib.urlopen(url).read())
    tree = etree.parse(gzip.GzipFile(fileobj=fo))
    return tree

def find_system_by_name(name):
    """Returns system node matching supplied name

    :param name: string containing desired system name.
    """
    systems = tree.findall('.//system')
    names = [system.find('name').text for system in systems]
    return systems[names.index(name.replace('_',' '))]

def most_recent_planet(tree):
    """Returns planet node that has most recent update date.

    :param tree: lxml etree
    :returns: lxml etree
    """
    updates = tree.findall('.//lastupdate')
    update_time = [time.strptime(date.text,'%y/%m/%d') for date in updates]
    planet = updates[update_time.index(max(update_time))].getparent()
    return planet

def most_recent_system(tree):
    """Returns system with most recently updated planet.

    :param tree: lxml etree
    """
    planet = most_recent_planet(tree)
    return get_parent_tag(planet, 'system')

def get_parent_tag(node, tag):
    """Traverses up the tree from node until it finds tag and then returns that node.

    :param node: The child node to start the search from
    :param tag: String with tag type we're searching for.
    """
    if node.tag == tag:
        return node
    else:
        return get_parent_tag(node.getparent(), tag)

def num_planets(tree):
    """Returns the total number of planets in the tree

    :param tree: lxml etree
    :returns: integer representing number of planets.
    """
    return int(tree.xpath("count(.//planet)"))

def num_stars(tree):
    """Returns the total number of stars in the tree

    :param tree: lxml etree
    :returns: integer representing number of planets.
    """
    return int(tree.xpath("count(.//star)"))

def num_systems(tree):
    """Returns the total number of systems in the tree

    :param tree: lxml etree
    :returns: integer representing number of planets.
    """
    return int(tree.xpath("count(.//system)"))

def largest_system(tree):
    """Returns system node that has the largest number of planets.

    :param tree: lxml etree
    :returns: lxml etree centered on system with the mode planets.
    """
    system_size = [num_planets(system) for system in tree.xpath(".//system")]
    largest_idx = system_size.index(max(system_size))
    root = tree.xpath("//systems")[0]
    return root[largest_idx]

def write_tree(tree,fn):
    xmlstr = etree.tostring(tree, pretty_print=True)
    with open(fn, 'w') as f:
        f.write(xmlstr)

def planet_name(planet):
    """Returns name of planet, just letter.
    """
    return planet.find('name').text.split(' ')[-1]

def spectral_name(star):
    """Retruns string containing spectral name of star, with colorama color

    Doesn't include ANSII code if colorama is not installed.
    :param star: lxml etree on <star> node
    """
    spectral = star.find('spectraltype').text
    stellarclass = spectral[0]
    if stellarclass in STARCOLOR.keys():
        return STARCOLOR[stellarclass] + spectral + RSTCOLOR
    else:
        return spectral

def summarize_star(star):
    """return one line summary of star"""
    if star.find('name').text[-2] == ' ':
        name = star.find('name').text[-1]
    else:
        name = ' '
    return u'{0} {1} {2}{3}'.format(name, spectral_name(star), star.find('mass').text, 'M'+SYMBOL['sun'])

def format_planet_mass_str(planet):
    """Takes float multiple of Jupiter mass, and returns it rounded and prefixed.

    Uses J2.3 for jupiter masses.
    Uses E3.4 for earth masses if under 0.05 jupiter masses.
    """
    jup_mass = get_planet_mass(planet, 'jupiter')
    planet_size = get_planet_size(planet)
    flt_mass = float(planet.find('mass').text)
    if planet_size.name == 'terrestrial':
        use_mass = get_planet_mass(planet, 'earth')
        symbol = SYMBOL['earth']
    else:
        use_mass = jup_mass
        symbol = SYMBOL['jupiter']
    return u'{:0.3f}M{}'.format(round(use_mass, 3), symbol)

def summarize_planet(planet):
    """Return one line summary of planet"""
    if planet.find('list').text == "Confirmed planets":
        reliable = ' '
    else:
        reliable = '?'

    letter = planet_name(planet)

    mass = format_planet_mass_str(planet)
    return u'{0} {1} {2}'.format(reliable, letter, mass)

def summarize_system(system):
    """Prints concise summary of system represented by tree

    :param system: ommi clxml etree based on <system> tag.
    """
    s = []
    s.append(system.find('name').text + ' - ' + str(num_stars(system)) + ' stars - ' + str(num_planets(system)) + ' planets')
    s.append(ascii_system(system))
    s.append('')
    binary = system.find('binary')
    if binary is not None:
        system = binary
    for star in system.iterfind('star'):
        s.append(' ' + summarize_star(star))
        for planet in star.iterfind('planet'):
            s.append('   ' + summarize_planet(planet))
    for planet in system.iterfind('planet'):
        print "circumbinary"
    return '\n'.join(s)

def get_max_sma(tree):
    """Returns max Semi-Major Axis (AU) for all planets in tree.

    :param tree: An lxml etree
    """
    allsmas = [float(x.text) for x in tree.findall('.//semimajoraxis')]
    return max(allsmas)

def ascii_system(system):
    """Return an ASCII graphic of the system.

    :param system: lxml etree based on <system> tag.
    """
    dots = []
    names = []
    maxsma = get_max_sma(system)
    for star in system.xpath('.//star'):
        t = [' ']*80
        d = [' ']*80
        d[0] = '*'
        for planet in star.iterfind('planet'):
            sma = float(planet.find('semimajoraxis').text)
            loc = int((sma / maxsma) * 78) + 1
            while t[loc] != ' ':
                loc += 1
            t[loc] = planet_name(planet)
            d[loc] = get_planet_size(planet).value
        names.append(''.join(t))
        dots.append(''.join(d))
    result = []
    for idx in range(len(dots)):
        result.append(dots[idx])
        result.append(names[idx])
    return '\n'.join(result)

def tweet_system(system):
    """Return system summary suitable for a twitter message.

    :param system: lxml etree based on <system> tag.
    """
    s = []
    for star in system.iterfind('star'):
        s.append(star.find('name').text)
        for planet in star.iterfind('planet'):
            planet_size = get_planet_size(planet)
            s.append('  ' + planet_size.value + ' ' + planet_name(planet))
    return '\n'.join(s)

def get_system_names(tree):
    """Returns list of all system names.

    :param tree: Tree to find systems in.
    """
    names = [n.replace(' ','_') for n in tree.xpath('./system/name/text()')]
    return names

class PlanetCmd(cmd.Cmd):
    def __init__(self, system_names):
        cmd.Cmd.__init__(self)
        self.prompt = '> '
        self.intro = "Exoplanet Explorer (type help commands):"
        self.system_names = system_names

    def do_most_recent_planet(self, args):
        planet = most_recent_planet(tree)
        print "Most recently updated planet is " + str(planet.find('name').text)
        print "  updated on " + planet.find('lastupdate').text
        print ""
        print planet.find('description').text

    def do_most_recent_system(self, args):
        print "Most recently updated system is:"
        most_recent = most_recent_system(tree)
        print summarize_system(most_recent)

    def do_stats(self, args):
        print "Catalog contains " + str(num_planets(tree)) + " planets in " + str(num_systems(tree)) + " systems."

    def do_largest_system(self, args):
        print "Largest system is:"
        largest = largest_system(tree)
        #write_tree(largest,'largest.xml')
        print summarize_system(largest)

    def do_system(self, system_name):
        print summarize_system(find_system_by_name(system_name))

    def do_tweet(self, system_name):
        print tweet_system(find_system_by_name(system_name))

    def complete_tweet(self, text, line, begidx, endidx):
        return self.complete_system(text, line, begidx, endidx)

    def complete_system(self, text, line, begidx, endidx):
        if not text:
            completions = self.system_names[:]
        else:
            completions = [ f
                            for f in self.system_names
                            if f.startswith(text)
                            ]
        return completions

    def do_exit(self, args):
        exit()

if __name__ == '__main__':
    tree = get_etree()
    system_names = get_system_names(tree)
    PlanetCmd(system_names).cmdloop()

<|endoftext|>


<commit_before>import sys

#sys.path.append('/home/02608/grantdel/pythonlib/lib/python2.7/site-packages')
import psycopg2
import datetime
import io
import math
import numpy
import scipy.stats as st

#Main method
def calc(stat_tbl, synfile, conn_info, pct, randits, outf):
    print "Table Containing Statistics: ", stat_tbl
    print "File path containing synsets: ", synfile
    print "which percentil to select: ", pct
    print "Number of iterations used to produce 'random word distribution'", randits
    print "out file that similarity scores will be written to: ", outf

    syn_link = {}

    with io.open(synfile, 'r', encoding='utf-8') as w:
        for line in w:
            row = line.strip().split('\t')
            for i in range(0, len(row)):
                linklist = [row[j] for j in range(0, len(row)) if j!=i]
                syn_link.setdefault(row[i], list()).extend(linklist)

    print syn_link["abode"]

    #Connecting to Database
    conn = psycopg2.connect(conn_info)
    print "Connecton Success"

    cur = conn.cursor()

    sim_dict = {}

    SQL_Fetch = "Select p1.gid, p1.stat from %s as p1 where p1.word = %s" % (stat_tbl, '%s')

    print "Calculating Similarity Scores for elements in synsets"

    for s in syn_link:
        #print s
        cur.execute(SQL_Fetch, (s, ))
        s_dict = dict([(x[0], float(x[1])) for x in cur.fetchall()])
        for s2 in syn_link[s]:
            #print "Comparing - ", s, " vs ", s2
            cur.execute(SQL_Fetch, (s2, ))
            s2_dict = dict([(x[0], float(x[1])) for x in cur.fetchall()])
            simscore = WeightedJac(s_dict, s2_dict)
            sim_dict[s+'|'+s2] = simscore

    print "Done calculating synset similarity scores"

    RandWordDist = RandomWord_SimDistribution(syn_link, cur, randits, stat_tbl)

    std_dev = numpy.std(RandWordDist)
    pop_mean = numpy.mean(RandWordDist)
    sig_hat = std_dev / math.sqrt(randits)

    openw = io.open(outf, 'w', encoding='utf-8')

    print "Writing to outfile"

    for p in sim_dict:
        pval1 = st.norm.sf((sim_dict[p] - mean) / std_dev)
        pval2 = st.norm.sf((sim_dict[p] - mean) / sighat)
        pval3 = st.norm.sf((mean - sim_dict[p]) / std_dev)
        pval4 = st.norm.sf((mean - sim_dict[p]) / sighat)
        print p, sim_dict[p], pval1, pval2, pval3, pval4
        openw.write(p +'\t' + str(sim_dict[p]) + '\t' + str(pop_mean) + '\t' + str(pval1) + '\t'+ str(pval2) + '\t' + str(pval3) + '\t' + str(pval4) + '\r\n')

    openw.close()
    conn.close()

def WeightedJac(d1, d2):
    minsum = sum([min(v, d2[k]) for k,v in d1.items()])
    maxsum = sum([max(v, d2[k]) for k,v in d1.items()])
    return (minsum/maxsum)

def RandomWord_SimDistribution(synlist, cur, randits, stat_tbl):
    print "Creating Random Word Distributions"
    x = 0
    SQL_Fetch = "Select p1.gid, p1.stat from %s as p1 where p1.word = %s" % (stat_tbl, '%s')
    keylist = synlist.keys()
    randJacScores = []
    while x < randits:
        r1 = random.randint(0, len(keylist))
        s1 = keylist[r1]
        r2 = random.randint(0, len(keylist))
        s2 = keylist[r1]
        if s1 != s2 and s1 not in synlist[s2] and s2 not in synlist[s1]:
            cur.execute(SQL_Fetch, (s1, ))
            s1_dict = dict([(x[0], float(x[1])) for x in cur.fetchall()])
            cur.execute(SQL_Fetch, (s2, ))
            s2_dict = dict([(x[0], float(x[1])) for x in cur.fetchall()])
            randJacScores.append(WeightedJac(s1_dict, s2_dict))
            x += 1
    print "Done Creating Random Word Distributions"
    return randJacScores

<commit_msg>JacSimilarity.py - debugging...<commit_after>import sys

#sys.path.append('/home/02608/grantdel/pythonlib/lib/python2.7/site-packages')
import psycopg2
import datetime
import io
import math
import numpy
import scipy.stats as st

#Main method
def calc(stat_tbl, synfile, conn_info, pct, randits, outf):
    print "Table Containing Statistics: ", stat_tbl
    print "File path containing synsets: ", synfile
    print "which percentil to select: ", pct
    print "Number of iterations used to produce 'random word distribution'", randits
    print "out file that similarity scores will be written to: ", outf

    syn_link = {}

    with io.open(synfile, 'r', encoding='utf-8') as w:
        for line in w:
            row = line.strip().split('\t')
            for i in range(0, len(row)):
                linklist = [row[j] for j in range(0, len(row)) if j!=i]
                syn_link.setdefault(row[i], list()).extend(linklist)

    print syn_link["abode"]

    #Connecting to Database
    conn = psycopg2.connect(conn_info)
    print "Connecton Success"

    cur = conn.cursor()

    sim_dict = {}

    SQL_Fetch = "Select p1.gid, p1.stat from %s as p1 where p1.word = %s" % (stat_tbl, '%s')

    print "Calculating Similarity Scores for elements in synsets"

    for s in syn_link:
        #print s
        cur.execute(SQL_Fetch, (s, ))
        s_dict = dict([(x[0], float(x[1])) for x in cur.fetchall()])
        if len(s_dict) > 0:
            for s2 in syn_link[s]:
                #print "Comparing - ", s, " vs ", s2
                cur.execute(SQL_Fetch, (s2, ))
                s2_dict = dict([(x[0], float(x[1])) for x in cur.fetchall()])
                if len(s2_dict) > 0:
                    simscore = WeightedJac(s_dict, s2_dict)
                    sim_dict[s+'|'+s2] = simscore

    print "Done calculating synset similarity scores"

    RandWordDist = RandomWord_SimDistribution(syn_link, cur, randits, stat_tbl)

    std_dev = numpy.std(RandWordDist)
    pop_mean = numpy.mean(RandWordDist)
    sig_hat = std_dev / math.sqrt(randits)

    openw = io.open(outf, 'w', encoding='utf-8')

    print "Writing to outfile"

    for p in sim_dict:
        pval1 = st.norm.sf((sim_dict[p] - mean) / std_dev)
        pval2 = st.norm.sf((sim_dict[p] - mean) / sighat)
        pval3 = st.norm.sf((mean - sim_dict[p]) / std_dev)
        pval4 = st.norm.sf((mean - sim_dict[p]) / sighat)
        print p, sim_dict[p], pval1, pval2, pval3, pval4
        openw.write(p +'\t' + str(sim_dict[p]) + '\t' + str(pop_mean) + '\t' + str(pval1) + '\t'+ str(pval2) + '\t' + str(pval3) + '\t' + str(pval4) + '\r\n')

    openw.close()
    conn.close()

def WeightedJac(d1, d2):
    minsum = sum([min(v, d2[k]) for k,v in d1.items()])
    maxsum = sum([max(v, d2[k]) for k,v in d1.items()])
    return (minsum/maxsum)

def RandomWord_SimDistribution(synlist, cur, randits, stat_tbl):
    print "Creating Random Word Distributions"
    x = 0
    SQL_Fetch = "Select p1.gid, p1.stat from %s as p1 where p1.word = %s" % (stat_tbl, '%s')
    keylist = synlist.keys()
    randJacScores = []
    while x < randits:
        r1 = random.randint(0, len(keylist))
        s1 = keylist[r1]
        r2 = random.randint(0, len(keylist))
        s2 = keylist[r1]
        if s1 != s2 and s1 not in synlist[s2] and s2 not in synlist[s1]:
            cur.execute(SQL_Fetch, (s1, ))
            s1_dict = dict([(x[0], float(x[1])) for x in cur.fetchall()])
            cur.execute(SQL_Fetch, (s2, ))
            s2_dict = dict([(x[0], float(x[1])) for x in cur.fetchall()])
            randJacScores.append(WeightedJac(s1_dict, s2_dict))
            x += 1
    print "Done Creating Random Word Distributions"
    return randJacScores

<|endoftext|>


<commit_before>import os
from PyQt4.QtCore import Qt, QDir, QFile, QFileInfo, QRect, QSize
from PyQt4.QtGui import (QColor, QFileDialog, QFileIconProvider, QFileSystemModel, QListView, QMenu, QPen, QPixmap, QStyle, QStyledItemDelegate)

class AttachmentItemDelegate(QStyledItemDelegate):

    def __init__(self, parent=None):
        super(AttachmentItemDelegate, self).__init__(parent)
        self.model = parent.model
        self.width = 96
        self.height = 128
        self.nameHeight = 48
        self.thumbHeight = self.height - self.nameHeight

    def paint(self, painter, option, index):
        filePath = self.model.filePath(index)
        fileName = self.model.fileName(index)
        r = option.rect

        img = QPixmap(filePath)
        if img.isNull():
            # If not image file, try to load icon with QFileIconProvider
            # according to file type (extension name).
            # Currently not work as intended.
            fileInfo = self.model.fileInfo(index)
            icon = QFileIconProvider().icon(fileInfo)
            img = icon.pixmap(QSize(32, 32))

        # Scale to height, align center horizontally, align bottom vertically.
        if img.height() > self.thumbHeight:
            img = img.scaledToHeight(self.thumbHeight, Qt.SmoothTransformation)
        imgLeft = (self.width - img.width()) / 2
        imgTop = self.thumbHeight - img.height()
        painter.drawPixmap(r.left()+imgLeft, r.top()+imgTop, img)

        # TODO: Rounding rectangle should be scaled to fileName length!
        if option.state & QStyle.State_Selected:
            painter.setBrush(Qt.darkBlue)
            # painter.drawRect(r.left()+imgLeft, r.top()+imgTop, img.width(), img.height())
            painter.drawRoundedRect(r.left(), r.top()+self.thumbHeight, self.width, self.nameHeight, 5, 5)
            pen = QPen(QColor.fromRgb(255, 255, 255), 1, Qt.SolidLine)
        else:
            pen = QPen(QColor.fromRgb(51, 51, 51), 1, Qt.SolidLine)

        painter.setPen(pen)
        painter.drawText(QRect(r.left(), r.top()+self.thumbHeight, self.width, self.nameHeight),
            Qt.AlignHCenter | Qt.TextWrapAnywhere, fileName)

    def sizeHint(self, option, index):
        return QSize(self.width + 16, self.height + 16)

class AttachmentView(QListView):
    """ A dockWidget displaying attachments of the current note.
    """

    def __init__(self, parent=None):
        super(AttachmentView, self).__init__(parent)
        self.parent = parent
        self.settings = parent.settings

        self.model = QFileSystemModel()
        self.model.setFilter(QDir.Files | QDir.AllDirs | QDir.NoDotAndDotDot | ~QDir.Dirs)
        self.model.setRootPath(self.settings.attachmentPath)
        self.setModel(self.model)

        # self.setRootIndex(self.model.index(self.settings.attachmentPath))
        self.setViewMode(QListView.IconMode)
        self.setUniformItemSizes(True)
        self.setResizeMode(QListView.Adjust)
        self.setItemDelegate(AttachmentItemDelegate(self))
        self.clicked.connect(self.click)

    def contextMenuEvent(self, event):
        menu = QMenu()
        indice = self.selectedIndexes()
        if len(indice):
            menu.addAction("Insert into note", self.insert)
            menu.addAction("Delete", self.delete)
        else:
            pass
        menu.exec_(event.globalPos())

    def mousePressEvent(self, event):
        """ Trigger click() when an item is pressed.
        """
        self.clearSelection()
        QListView.mousePressEvent(self, event)

    def mouseReleaseEvent(self, event):
        """ Trigger click() when an item is pressed.
        """
        self.clearSelection()
        QListView.mouseReleaseEvent(self, event)

    def insert(self):
        indice = self.selectedIndexes()
        for i in indice:
            filePath = self.model.filePath(i)
            filePath = filePath.replace(self.settings.notebookPath, "..")
            fileName = os.path.basename(filePath)
            text = "![%s](%s)" % (fileName, filePath)
            self.parent.notesEdit.insertPlainText(text)

    def delete(self):
        indice = self.selectedIndexes()
        for i in indice:
            filePath = self.model.filePath(i)
            QFile(filePath).remove()

    def click(self, index):
        self.setCurrentIndex(index)
<commit_msg>Fix bounding rect of filename in attachmentView.<commit_after>import os
from PyQt4.QtCore import Qt, QDir, QFile, QFileInfo, QRect, QSize
from PyQt4.QtGui import (QColor, QFileDialog, QFileIconProvider, QFileSystemModel, QListView, QMenu, QPen, QPixmap, QStyle, QStyledItemDelegate)

class AttachmentItemDelegate(QStyledItemDelegate):

    def __init__(self, parent=None):
        super(AttachmentItemDelegate, self).__init__(parent)
        self.model = parent.model
        self.width = 96
        self.height = 128
        self.nameHeight = 48
        self.thumbHeight = self.height - self.nameHeight

    def paint(self, painter, option, index):
        filePath = self.model.filePath(index)
        fileName = self.model.fileName(index)
        r = option.rect

        img = QPixmap(filePath)
        if img.isNull():
            # If not image file, try to load icon with QFileIconProvider
            # according to file type (extension name).
            # Currently not work as intended.
            fileInfo = self.model.fileInfo(index)
            icon = QFileIconProvider().icon(fileInfo)
            img = icon.pixmap(QSize(32, 32))

        # Scale to height, align center horizontally, align bottom vertically.
        if img.height() > self.thumbHeight:
            img = img.scaledToHeight(self.thumbHeight, Qt.SmoothTransformation)
        imgLeft = (self.width - img.width()) / 2
        imgTop = self.thumbHeight - img.height()
        painter.drawPixmap(r.left()+imgLeft, r.top()+imgTop, img)

        rect = QRect(r.left(), r.top()+self.thumbHeight, self.width, self.nameHeight)
        flag = Qt.AlignHCenter | Qt.TextWrapAnywhere
        # get the bounding rectangle of the fileName
        bdRect = painter.boundingRect(rect, flag, fileName)
        if bdRect.height() < rect.height():
            rect = bdRect

        if option.state & QStyle.State_Selected:
            painter.setBrush(Qt.darkBlue)
            painter.drawRoundedRect(rect, 5, 5)
            pen = QPen(QColor.fromRgb(255, 255, 255), 1, Qt.SolidLine)
        else:
            pen = QPen(QColor.fromRgb(51, 51, 51), 1, Qt.SolidLine)

        painter.setPen(pen)
        painter.drawText(rect, flag, fileName)

    def sizeHint(self, option, index):
        return QSize(self.width + 16, self.height + 16)

class AttachmentView(QListView):
    """ A dockWidget displaying attachments of the current note.
    """

    def __init__(self, parent=None):
        super(AttachmentView, self).__init__(parent)
        self.parent = parent
        self.settings = parent.settings

        self.model = QFileSystemModel()
        self.model.setFilter(QDir.Files | QDir.AllDirs | QDir.NoDotAndDotDot | ~QDir.Dirs)
        self.model.setRootPath(self.settings.attachmentPath)
        self.setModel(self.model)

        # self.setRootIndex(self.model.index(self.settings.attachmentPath))
        self.setViewMode(QListView.IconMode)
        self.setUniformItemSizes(True)
        self.setResizeMode(QListView.Adjust)
        self.setItemDelegate(AttachmentItemDelegate(self))
        self.clicked.connect(self.click)

    def contextMenuEvent(self, event):
        menu = QMenu()
        indice = self.selectedIndexes()
        if len(indice):
            menu.addAction("Insert into note", self.insert)
            menu.addAction("Delete", self.delete)
        else:
            pass
        menu.exec_(event.globalPos())

    def mousePressEvent(self, event):
        """ Trigger click() when an item is pressed.
        """
        self.clearSelection()
        QListView.mousePressEvent(self, event)

    def mouseReleaseEvent(self, event):
        """ Trigger click() when an item is pressed.
        """
        self.clearSelection()
        QListView.mouseReleaseEvent(self, event)

    def insert(self):
        indice = self.selectedIndexes()
        for i in indice:
            filePath = self.model.filePath(i)
            filePath = filePath.replace(self.settings.notebookPath, "..")
            fileName = os.path.basename(filePath)
            text = "![%s](%s)" % (fileName, filePath)
            self.parent.notesEdit.insertPlainText(text)

    def delete(self):
        indice = self.selectedIndexes()
        for i in indice:
            filePath = self.model.filePath(i)
            QFile(filePath).remove()

    def click(self, index):
        self.setCurrentIndex(index)
<|endoftext|>


<commit_before>from __future__ import print_function

import sys
import time
import yaml
import os
import os.path
import logging
import shutil

LOG = logging.getLogger(__name__)

COMMITTED_CONFIG = 'committed-config.yml'
RUNNING_CONFIG = 'running-config.yml'
PROTOTYPE_ENV = 'MINEMELD_PROTOTYPE_PATH'

def _load_node_prototype(protoname):
    paths = os.getenv(PROTOTYPE_ENV, None)
    if paths is None:
        raise RuntimeError('Unable to load prototype %s: %s '
                           'environment variable not set' %
                           (protoname, PROTOTYPE_ENV))
    paths = paths.split(':')

    proto_module, proto_name = protoname.rsplit('.', 1)

    pmodule = None
    pmprotos = {}
    for p in paths:
        pmpath = os.path.join(p, proto_module+'.yml')

        try:
            with open(pmpath, 'r') as pf:
                pmodule = yaml.safe_load(pf)

                if pmodule is None:
                    pmodule = {}
        except IOError:
            pmodule = None
            continue

        pmprotos = pmodule.get('prototypes', {})

        if proto_name not in pmprotos:
            pmodule = None
            continue

        if 'class' not in pmprotos[proto_name]:
            pmodule = None
            continue

        return pmprotos[proto_name]

    raise RuntimeError('Unable to load prototype %s: '
                       ' not found' % (protoname))

def _load_config_from_file(f):
    config = yaml.safe_load(f)

    if config is None:
        config = {}

    if 'fabric' not in config:
        config['fabric'] = {
            'class': 'AMQP',
            'config': {
                'num_connections': 5
            }
        }

    if 'mgmtbus' not in config:
        config['mgmtbus'] = {
            'transport': {
                'class': 'AMQP',
                'config': {}
            },
            'master': {},
            'slave': {}
        }

    nodes_config = config.get('nodes', {})
    for nname, nconfig in nodes_config.iteritems():
        if 'prototype' in nconfig:
            nproto = _load_node_prototype(nconfig['prototype'])

            nconfig.pop('prototype')

            nconfig['class'] = nproto['class']
            nproto_config = nproto.get('config', {})
            nproto_config.update(
                nconfig.get('config', {})
            )
            nconfig['config'] = nproto_config

    return config

def _load_config_from_dir(path):
    ccpath = os.path.join(
        path,
        COMMITTED_CONFIG
    )
    rcpath = os.path.join(
        path,
        RUNNING_CONFIG
    )

    cconfig = None
    if os.path.exists(ccpath):
        with open(ccpath, 'r') as cf:
            cconfig = _load_config_from_file(cf)

    rcconfig = None
    if os.path.exists(rcpath):
        with open(rcpath, 'r') as cf:
            rcconfig = _load_config_from_file(cf)

    if rcconfig is None and cconfig is None:
        print(
            "At least one of", RUNNING_CONFIG,
            "or", COMMITTED_CONFIG,
            "should exist in", path,
            file=sys.stderr
        )
        sys.exit(1)
    elif rcconfig is not None and cconfig is None:
        rcconfig['newconfig'] = False
        return rcconfig
    elif rcconfig is None and cconfig is not None:
        shutil.copyfile(ccpath, rcpath)
        cconfig['newconfig'] = True
        return cconfig
    elif rcconfig is not None and cconfig is not None:
        # ugly
        if yaml.dump(cconfig) != yaml.dump(rcconfig):
            shutil.copyfile(rcpath, rcpath+'.%d' % int(time.time()))
            shutil.copyfile(ccpath, rcpath)
            cconfig['newconfig'] = True
            return cconfig

        rcconfig['newconfig'] = False
        return rcconfig

def load_config(config_path):
    if os.path.isdir(config_path):
        return _load_config_from_dir(config_path)

    with open(config_path, 'r') as cf:
        config = _load_config_from_file(cf)

    config['newconfig'] = True

    return config
<commit_msg>added validation<commit_after>from __future__ import print_function

import sys
import time
import yaml
import os
import os.path
import logging
import shutil

LOG = logging.getLogger(__name__)

COMMITTED_CONFIG = 'committed-config.yml'
RUNNING_CONFIG = 'running-config.yml'
PROTOTYPE_ENV = 'MINEMELD_PROTOTYPE_PATH'

def _load_node_prototype(protoname):
    paths = os.getenv(PROTOTYPE_ENV, None)
    if paths is None:
        raise RuntimeError('Unable to load prototype %s: %s '
                           'environment variable not set' %
                           (protoname, PROTOTYPE_ENV))
    paths = paths.split(':')

    proto_module, proto_name = protoname.rsplit('.', 1)

    pmodule = None
    pmprotos = {}
    for p in paths:
        pmpath = os.path.join(p, proto_module+'.yml')

        try:
            with open(pmpath, 'r') as pf:
                pmodule = yaml.safe_load(pf)

                if pmodule is None:
                    pmodule = {}
        except IOError:
            pmodule = None
            continue

        pmprotos = pmodule.get('prototypes', {})

        if proto_name not in pmprotos:
            pmodule = None
            continue

        if 'class' not in pmprotos[proto_name]:
            pmodule = None
            continue

        return pmprotos[proto_name]

    raise RuntimeError('Unable to load prototype %s: '
                       ' not found' % (protoname))

def _load_config_from_file(f):
    config = yaml.safe_load(f)

    if config is None:
        config = {}

    if 'fabric' not in config:
        config['fabric'] = {
            'class': 'AMQP',
            'config': {
                'num_connections': 5
            }
        }

    if 'mgmtbus' not in config:
        config['mgmtbus'] = {
            'transport': {
                'class': 'AMQP',
                'config': {}
            },
            'master': {},
            'slave': {}
        }

    nodes_config = config.get('nodes', {})
    for nname, nconfig in nodes_config.iteritems():
        if 'prototype' in nconfig:
            nproto = _load_node_prototype(nconfig['prototype'])

            nconfig.pop('prototype')

            nconfig['class'] = nproto['class']
            nproto_config = nproto.get('config', {})
            nproto_config.update(
                nconfig.get('config', {})
            )
            nconfig['config'] = nproto_config

    return config

def _load_config_from_dir(path):
    ccpath = os.path.join(
        path,
        COMMITTED_CONFIG
    )
    rcpath = os.path.join(
        path,
        RUNNING_CONFIG
    )

    cconfig = None
    if os.path.exists(ccpath):
        with open(ccpath, 'r') as cf:
            cconfig = _load_config_from_file(cf)

    rcconfig = None
    if os.path.exists(rcpath):
        with open(rcpath, 'r') as cf:
            rcconfig = _load_config_from_file(cf)

    if rcconfig is None and cconfig is None:
        print(
            "At least one of", RUNNING_CONFIG,
            "or", COMMITTED_CONFIG,
            "should exist in", path,
            file=sys.stderr
        )
        sys.exit(1)
    elif rcconfig is not None and cconfig is None:
        rcconfig['newconfig'] = False
        return rcconfig
    elif rcconfig is None and cconfig is not None:
        shutil.copyfile(ccpath, rcpath)
        cconfig['newconfig'] = True
        return cconfig
    elif rcconfig is not None and cconfig is not None:
        # ugly
        if yaml.dump(cconfig) != yaml.dump(rcconfig):
            shutil.copyfile(rcpath, rcpath+'.%d' % int(time.time()))
            shutil.copyfile(ccpath, rcpath)
            cconfig['newconfig'] = True
            return cconfig

        rcconfig['newconfig'] = False
        return rcconfig

def _detect_cycles(nodes):
    # using Topoligical Sorting to detect cycles in graph, see Wikipedia
    graph = {}
    S = set()
    L = []

    for n in nodes:
        graph[n] = {
            'inputs': [],
            'outputs': []
        }

    for n, v in nodes.iteritems():
        for i in v.get('inputs', []):
            if i in graph:
                graph[i]['outputs'].append(n)
                graph[n]['inputs'].append(i)

    for n, v in graph.iteritems():
        if len(v['inputs']) == 0:
            S.add(n)

    while len(S) != 0:
        n = S.pop()
        L.append(n)

        for m in graph[n]['outputs']:
            graph[m]['inputs'].remove(n)
            if len(graph[m]['inputs']) == 0:
                S.add(m)
        graph[n]['outputs'] = []

    nedges = 0
    for n, v in graph.iteritems():
        nedges += len(v['inputs'])
        nedges += len(v['outputs'])

    return nedges == 0

def validate_config(config):
    result = []

    nodes = config['nodes']

    for n, v in nodes.iteritems():
        for i in v.get('inputs', []):
            if i not in nodes:
                result.append('%s -> %s is unknown' % (n, i))
                continue

            if not nodes[i].get('output', False):
                result.append('%s -> %s output disabled' %
                              (n, i))

    if not _detect_cycles(nodes):
        result.append('loop detected')

    return result

def load_config(config_path):
    if os.path.isdir(config_path):
        return _load_config_from_dir(config_path)

    with open(config_path, 'r') as cf:
        config = _load_config_from_file(cf)

    config['newconfig'] = True

    return config
<|endoftext|>


<commit_before>"""Class that defines the board for Mine Sweeper game.

Author: Yuhuang Hu
Email : duguyue100@gmail.com
"""

import numpy as np

class MSBoard(object):
    """Define a Mine Sweeper Game Board."""

    def __init__(self, board_width, board_height, num_mines):
        """The init function of Mine Sweeper Game.

        Parameters
        ----------
        board_width : int
            the width of the board (> 0)
        board_height : int
            the height of the board (> 0)
        num_mines : int
            the number of mines, cannot be larger than
            (board_width x board_height)
        """
        if (board_width <= 0):
            raise ValueError("the board width cannot be non-positive!")
        else:
            self.board_width = board_width

        if (board_height <= 0):
            raise ValueError("the board height cannot be non-positive!")
        else:
            self.board_height = board_width

        if (num_mines >= (board_width*board_height)):
            raise ValueError("The number of mines cannot be larger than "
                             "number of grids!")
        else:
            self.num_mines = num_mines

    def init_board(self):
        """Init a valid board by given settings.

        Parameters
        ----------
        mine_map : numpy.ndarray
            the map that defines the mine
            0 is empty, 1 is mine
        info_map : numpy.ndarray
            the map that presents to gamer
            0-8 is number of mines in srrounding.
            9 is flagged field.
            10 is questioned field.
            11 is undiscovered field.
        """
        self.mine_map = np.zeros((self.board_height, self.board_width),
                                 dtype=np.uint8)
        idx_list = np.random.permutation(self.board_width*self.board_height)
        idx_list = idx_list[:self.num_mines]

        for idx in idx_list:
            idx_x = int(idx % self.board_width)
            idx_y = int(idx / self.board_width)

            self.mine_map[idx_y, idx_x] = 1

        self.info_map = np.ones((self.board_height, self.board_width),
                                dtype=np.uint8)*11

    def click_field(self, move_x, move_y):
        """Click one grid by given position."""
        field_status = self.info_map[move_x, move_y]

        # can only click blank region
        if field_status == 11:
            # discover function for 0s
            tl_idx, br_idx = self.get_region(move_x, move_y)

        self.check_board()

    def get_region(self, move_x, move_y):
        """Get region around a location."""
        top_left = (move_y-1, move_x-1)
        bottom_right = (move_y+1, move_x+1)

        return top_left, bottom_right

    def flag_field(self, move_x, move_y):
        """Flag a grid by given position."""
        field_status = self.info_map[move_x, move_y]

        # a questioned or undiscovered field
        if field_status != 9 and (field_status == 10 or field_status == 11):
            self.info_map[move_y, move_x] = 11

        self.check_board()

    def unflag_field(self, move_x, move_y):
        """Unflag or unquestion a grid by given position."""
        field_status = self.info_map[move_x, move_y]

        if field_status == 9 or field_status == 10:
            self.info_map[move_y, move_x] = 11

    def question_field(self, move_x, move_y):
        """Question a grid by given position."""
        field_status = self.info_map[move_x, move_y]

        # a questioned or undiscovered field
        if field_status != 10 and (field_status == 9 or field_status == 11):
            self.info_map[move_y, move_x] = 10

        self.check_board()

    def check_board(self):
        """Check the board status and give feedback."""
<commit_msg>check board and fix bugs<commit_after>"""Class that defines the board for Mine Sweeper game.

Author: Yuhuang Hu
Email : duguyue100@gmail.com
"""

import numpy as np

class MSBoard(object):
    """Define a Mine Sweeper Game Board."""

    def __init__(self, board_width, board_height, num_mines):
        """The init function of Mine Sweeper Game.

        Parameters
        ----------
        board_width : int
            the width of the board (> 0)
        board_height : int
            the height of the board (> 0)
        num_mines : int
            the number of mines, cannot be larger than
            (board_width x board_height)
        """
        if (board_width <= 0):
            raise ValueError("the board width cannot be non-positive!")
        else:
            self.board_width = board_width

        if (board_height <= 0):
            raise ValueError("the board height cannot be non-positive!")
        else:
            self.board_height = board_width

        if (num_mines >= (board_width*board_height)):
            raise ValueError("The number of mines cannot be larger than "
                             "number of grids!")
        else:
            self.num_mines = num_mines

        self.init_board()

    def init_board(self):
        """Init a valid board by given settings.

        Parameters
        ----------
        mine_map : numpy.ndarray
            the map that defines the mine
            0 is empty, 1 is mine
        info_map : numpy.ndarray
            the map that presents to gamer
            0-8 is number of mines in srrounding.
            9 is flagged field.
            10 is questioned field.
            11 is undiscovered field.
            12 is a mine field.
        """
        self.mine_map = np.zeros((self.board_height, self.board_width),
                                 dtype=np.uint8)
        idx_list = np.random.permutation(self.board_width*self.board_height)
        idx_list = idx_list[:self.num_mines]

        for idx in idx_list:
            idx_x = int(idx % self.board_width)
            idx_y = int(idx / self.board_width)

            self.mine_map[idx_y, idx_x] = 1

        self.info_map = np.ones((self.board_height, self.board_width),
                                dtype=np.uint8)*11

    def click_field(self, move_x, move_y):
        """Click one grid by given position."""
        field_status = self.info_map[move_y, move_x]

        # can only click blank region
        if field_status == 11:
            if self.mine_map[move_y, move_x] == 1:
                self.info_map[move_y, move_x] = 12
            else:
                # discover the region.
                self.discover_region(move_x, move_y)
                # discover function for 0s
                # something BFS

    def discover_region(self, move_x, move_y):
        """Discover region from given location."""

    def get_region(self, move_x, move_y):
        """Get region around a location."""
        top_left = (max(move_y-1, 0), max(move_x-1, 0))
        bottom_right = (min(move_y+1, self.board_width),
                        min(move_x+1, self.board_height))

        return top_left, bottom_right

    def flag_field(self, move_x, move_y):
        """Flag a grid by given position."""
        field_status = self.info_map[move_y, move_x]

        # a questioned or undiscovered field
        if field_status != 9 and (field_status == 10 or field_status == 11):
            self.info_map[move_y, move_x] = 9

    def unflag_field(self, move_x, move_y):
        """Unflag or unquestion a grid by given position."""
        field_status = self.info_map[move_y, move_x]

        if field_status == 9 or field_status == 10:
            self.info_map[move_y, move_x] = 11

    def question_field(self, move_x, move_y):
        """Question a grid by given position."""
        field_status = self.info_map[move_y, move_x]

        # a questioned or undiscovered field
        if field_status != 10 and (field_status == 9 or field_status == 11):
            self.info_map[move_y, move_x] = 10

    def check_board(self):
        """Check the board status and give feedback."""
        num_mines = np.sum(self.info_map == 12)
        num_undiscovered = np.sum(self.info_map == 11)
        num_questioned = np.sum(self.info_map == 10)

        if num_mines > 0:
            return 0
        elif num_undiscovered > 0 or num_questioned > 0:
            return 2
        elif np.array_equal(self.info_map == 9, self.mine_map):
            return 1
<|endoftext|>