<commit_before>#!/usr/bin/env python
# vim: set sw=4 et:

import logging
import os, sys, argparse
# logging.basicConfig(stream=sys.stdout, level=logging.INFO,
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG,
        format='%(asctime)s %(process)d %(levelname)s %(threadName)s %(name)s.%(funcName)s(%(filename)s:%(lineno)d) %(message)s')

from json import dumps, loads
import urllib.request, urllib.error, urllib.parse
from itertools import count
import websocket
import time
import uuid
import threading
import subprocess
import signal
from kombu import Connection, Exchange, Queue
import tempfile
from umbra import behaviors

class UmbraWorker:
    logger = logging.getLogger('umbra.UmbraWorker')

    def __init__(self, umbra, chrome_port=9222, chrome_exe='chromium-browser', chrome_wait=10, client_id='request'):
        self.command_id = count(1)
        self.lock = threading.Lock()
        self.umbra = umbra
        self.chrome_port = chrome_port
        self.chrome_exe = chrome_exe
        self.chrome_wait = chrome_wait
        self.client_id = client_id
        self.page_done = threading.Event()
        self.idle_timer = None
        self.hard_stop_timer = None

    def browse_page(self, url, url_metadata):
        with self.lock:
            self.url = url
            self.url_metadata = url_metadata
            with tempfile.TemporaryDirectory() as user_data_dir:
                with Chrome(self.chrome_port, self.chrome_exe, self.chrome_wait, user_data_dir) as websocket_url:
                    websock = websocket.WebSocketApp(websocket_url,
                            on_open=self.visit_page, on_message=self.handle_message)
                    websock_thread = threading.Thread(target=websock.run_forever)
                    websock_thread.start()

                    self.page_done.clear()
                    self._reset_idle_timer()
                    while not self.page_done.is_set():
                        time.sleep(0.5)

                    websock.close()
                    self.idle_timer = None

    def _reset_idle_timer(self):
        def _idle_timeout():
            self.logger.debug('idle timeout')
            self.page_done.set()
            if self.hard_stop_timer:
                self.hard_stop_timer.cancel()

        def _hard_timeout():
            self.logger.debug('hard timeout')
            self.page_done.set()
            if self.idle_timer:
                self.idle_timer.cancel()

        if self.idle_timer:
            self.idle_timer.cancel()

        self.idle_timer = threading.Timer(30, _idle_timeout)
        self.idle_timer.start()

        if not self.hard_stop_timer: # 15 minutes is as long as we should give 1 page
            self.hard_stop_timer = threading.Timer(900, _hard_timeout)
            self.hard_stop_timer.start()

    def visit_page(self, websock):
        msg = dumps(dict(method="Network.enable", id=next(self.command_id)))
        self.logger.debug('sending message to {}: {}'.format(websock, msg))
        websock.send(msg)

        msg = dumps(dict(method="Page.enable", id=next(self.command_id)))
        self.logger.debug('sending message to {}: {}'.format(websock, msg))
        websock.send(msg)

        msg = dumps(dict(method="Console.enable", id=next(self.command_id)))
        self.logger.debug('sending message to {}: {}'.format(websock, msg))
        websock.send(msg)

        msg = dumps(dict(method="Page.navigate", id=next(self.command_id), params={"url": self.url}))
        self.logger.debug('sending message to {}: {}'.format(websock, msg))
        websock.send(msg)

    def send_request_to_amqp(self, chrome_msg):
        payload = chrome_msg['params']['request']
        payload['parentUrl'] = self.url
        payload['parentUrlMetadata'] = self.url_metadata
        self.logger.debug('sending to amqp exchange={} routing_key={} payload={}'.format(self.umbra.umbra_exchange.name, self.client_id, payload))
        with self.umbra.producer_lock:
            self.umbra.producer.publish(payload,
                    exchange=self.umbra.umbra_exchange,
                    routing_key=self.client_id)

    def handle_message(self, websock, message):
        # self.logger.debug("message from {} - {}".format(websock.url, message[:95]))
        # self.logger.debug("message from {} - {}".format(websock.url, message))
        message = loads(message)
        if "method" in message and message["method"] == "Network.requestWillBeSent":
            self._reset_idle_timer()
            if not message["params"]["request"]["url"].lower().startswith("data:"):
                self.send_request_to_amqp(message)
            else:
                self.logger.debug("ignoring data url {}".format(message["params"]["request"]["url"][:80]))
        elif "method" in message and message["method"] == "Page.loadEventFired":
            self.logger.debug("Page.loadEventFired, starting behaviors url={} message={}".format(self.url, message))
            behaviors.execute(self.url, websock, self.command_id)
        elif "method" in message and message["method"] == "Console.messageAdded":
            self.logger.debug("{} console {} {}".format(websock.url,
                message["params"]["message"]["level"],
                message["params"]["message"]["text"]))

class Umbra:
    logger = logging.getLogger('umbra.Umbra')

    def __init__(self, amqp_url, chrome_exe, browser_wait):
        self.amqp_url = amqp_url
        self.chrome_exe = chrome_exe
        self.browser_wait = browser_wait
        self.producer = None
        self.producer_lock = None
        self.workers = {}
        self.workers_lock = threading.Lock()
        self.amqp_thread = threading.Thread(target=self.consume_amqp)
        self.amqp_stop = threading.Event()
        self.amqp_thread.start()

    def shutdown(self):
        self.logger.info("shutting down amqp consumer {}".format(self.amqp_url))
        self.amqp_stop.set()
        self.amqp_thread.join()

    def consume_amqp(self):
        while not self.amqp_stop.is_set():
            try:
                self.umbra_exchange = Exchange(name='umbra', type='direct', durable=True)
                url_queue = Queue('urls', routing_key='url', exchange=self.umbra_exchange)
                self.logger.info("connecting to amqp exchange={} at {}".format(self.umbra_exchange.name, self.amqp_url))
                with Connection(self.amqp_url) as conn:
                    if self.producer_lock is None:
                        self.producer_lock = threading.Lock()
                    with self.producer_lock:
                        self.producer = conn.Producer(serializer='json')
                    with conn.Consumer(url_queue, callbacks=[self.fetch_url]) as consumer:
                        import socket
                        while not self.amqp_stop.is_set():
                            import random
                            if random.randrange(0, 30) == 0:
                                raise BaseException("test exception!")
                            try:
                                conn.drain_events(timeout=0.5)
                            except socket.timeout:
                                pass
            except BaseException as e:
                self.logger.error("amqp exception {}".format(e))
                self.logger.error("attempting to reopen amqp connection")

    def fetch_url(self, body, message):
        client_id = body['clientId']
        with self.workers_lock:
            if not client_id in self.workers:
                port = 9222 + len(self.workers)
                t = UmbraWorker(umbra=self, chrome_port=port,
                        chrome_exe=self.chrome_exe,
                        chrome_wait=self.browser_wait,
                        client_id=client_id)
                self.workers[client_id] = t

        def browse_page_async():
            self.logger.info('client_id={} body={}'.format(client_id, body))
            self.workers[client_id].browse_page(body['url'], body['metadata'])

        threading.Thread(target=browse_page_async).start()

        message.ack()

class Chrome:
    logger = logging.getLogger('umbra.Chrome')

    def __init__(self, port, executable, browser_wait, user_data_dir):
        self.port = port
        self.executable = executable
        self.browser_wait = browser_wait
        self.user_data_dir = user_data_dir

    def fetch_debugging_json():
        raw_json = urllib.request.urlopen("http://localhost:%s/json" % self.port).read()
        json = raw_json.decode('utf-8')
        return loads(json)

    # returns websocket url to chrome window with about:blank loaded
    def __enter__(self):
        chrome_args = [self.executable,
                "--user-data-dir={}".format(self.user_data_dir),
                "--remote-debugging-port=%s" % self.port,
                "--disable-web-sockets", "--disable-cache",
                "--window-size=1100,900", "--enable-logging",
                "--homepage=about:blank", "about:blank"]
        self.logger.info("running {}".format(chrome_args))
        self.chrome_process = subprocess.Popen(chrome_args, start_new_session=True)
        self.logger.info("chrome running, pid {}".format(self.chrome_process.pid))
        start = time.time()

        json_url = "http://localhost:%s/json" % self.port

        while True:
            try:
                raw_json = urllib.request.urlopen(json_url).read()
                all_debug_info = loads(raw_json.decode('utf-8'))
                debug_info = [x for x in all_debug_info if x['url'] == 'about:blank']

                if debug_info and 'webSocketDebuggerUrl' in debug_info[0]:
                    self.logger.debug("{} returned {}".format(json_url, raw_json))
                    url = debug_info[0]['webSocketDebuggerUrl']
                    self.logger.info('got chrome window websocket debug url {} from {}'.format(url, json_url))
                    return url
            except:
                pass
            finally:
                if time.time() - start > float(self.browser_wait):
                    raise Exception("failed to retrieve {} after {} seconds".format(json_url, time.time() - start))
                else:
                    time.sleep(0.5)

    def __exit__(self, *args):
        self.logger.info("killing chrome pid {}".format(self.chrome_process.pid))
        os.killpg(self.chrome_process.pid, signal.SIGINT)
        self.chrome_process.wait()

def main():
    import faulthandler
    faulthandler.register(signal.SIGQUIT)

    arg_parser = argparse.ArgumentParser(prog=os.path.basename(sys.argv[0]),
            description='umbra - Browser automation tool',
            formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    arg_parser.add_argument('-w', '--browser-wait', dest='browser_wait', default='10',
            help='Seconds to wait for browser initialization')
    arg_parser.add_argument('-e', '--executable', dest='executable', default='chromium-browser',
            help='Executable to use to invoke chrome')
    arg_parser.add_argument('-u', '--url', dest='amqp_url', default='amqp://guest:guest@localhost:5672/%2f',
            help='URL identifying the amqp server to talk to')
    args = arg_parser.parse_args(args=sys.argv[1:])

    umbra = Umbra(args.amqp_url, args.executable, args.browser_wait)
    try:
        while True:
            time.sleep(0.5)
    except:
        pass
    finally:
        umbra.shutdown()

if __name__ == "__main__":
    main()

<commit_msg>remove exception raised for testing<commit_after>#!/usr/bin/env python
# vim: set sw=4 et:

import logging
import os, sys, argparse
# logging.basicConfig(stream=sys.stdout, level=logging.INFO,
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG,
        format='%(asctime)s %(process)d %(levelname)s %(threadName)s %(name)s.%(funcName)s(%(filename)s:%(lineno)d) %(message)s')

from json import dumps, loads
import urllib.request, urllib.error, urllib.parse
from itertools import count
import websocket
import time
import uuid
import threading
import subprocess
import signal
from kombu import Connection, Exchange, Queue
import tempfile
from umbra import behaviors

class UmbraWorker:
    logger = logging.getLogger('umbra.UmbraWorker')

    def __init__(self, umbra, chrome_port=9222, chrome_exe='chromium-browser', chrome_wait=10, client_id='request'):
        self.command_id = count(1)
        self.lock = threading.Lock()
        self.umbra = umbra
        self.chrome_port = chrome_port
        self.chrome_exe = chrome_exe
        self.chrome_wait = chrome_wait
        self.client_id = client_id
        self.page_done = threading.Event()
        self.idle_timer = None
        self.hard_stop_timer = None

    def browse_page(self, url, url_metadata):
        with self.lock:
            self.url = url
            self.url_metadata = url_metadata
            with tempfile.TemporaryDirectory() as user_data_dir:
                with Chrome(self.chrome_port, self.chrome_exe, self.chrome_wait, user_data_dir) as websocket_url:
                    websock = websocket.WebSocketApp(websocket_url,
                            on_open=self.visit_page, on_message=self.handle_message)
                    websock_thread = threading.Thread(target=websock.run_forever)
                    websock_thread.start()

                    self.page_done.clear()
                    self._reset_idle_timer()
                    while not self.page_done.is_set():
                        time.sleep(0.5)

                    websock.close()
                    self.idle_timer = None

    def _reset_idle_timer(self):
        def _idle_timeout():
            self.logger.debug('idle timeout')
            self.page_done.set()
            if self.hard_stop_timer:
                self.hard_stop_timer.cancel()

        def _hard_timeout():
            self.logger.debug('hard timeout')
            self.page_done.set()
            if self.idle_timer:
                self.idle_timer.cancel()

        if self.idle_timer:
            self.idle_timer.cancel()

        self.idle_timer = threading.Timer(30, _idle_timeout)
        self.idle_timer.start()

        if not self.hard_stop_timer: # 15 minutes is as long as we should give 1 page
            self.hard_stop_timer = threading.Timer(900, _hard_timeout)
            self.hard_stop_timer.start()

    def visit_page(self, websock):
        msg = dumps(dict(method="Network.enable", id=next(self.command_id)))
        self.logger.debug('sending message to {}: {}'.format(websock, msg))
        websock.send(msg)

        msg = dumps(dict(method="Page.enable", id=next(self.command_id)))
        self.logger.debug('sending message to {}: {}'.format(websock, msg))
        websock.send(msg)

        msg = dumps(dict(method="Console.enable", id=next(self.command_id)))
        self.logger.debug('sending message to {}: {}'.format(websock, msg))
        websock.send(msg)

        msg = dumps(dict(method="Page.navigate", id=next(self.command_id), params={"url": self.url}))
        self.logger.debug('sending message to {}: {}'.format(websock, msg))
        websock.send(msg)

    def send_request_to_amqp(self, chrome_msg):
        payload = chrome_msg['params']['request']
        payload['parentUrl'] = self.url
        payload['parentUrlMetadata'] = self.url_metadata
        self.logger.debug('sending to amqp exchange={} routing_key={} payload={}'.format(self.umbra.umbra_exchange.name, self.client_id, payload))
        with self.umbra.producer_lock:
            self.umbra.producer.publish(payload,
                    exchange=self.umbra.umbra_exchange,
                    routing_key=self.client_id)

    def handle_message(self, websock, message):
        # self.logger.debug("message from {} - {}".format(websock.url, message[:95]))
        # self.logger.debug("message from {} - {}".format(websock.url, message))
        message = loads(message)
        if "method" in message and message["method"] == "Network.requestWillBeSent":
            self._reset_idle_timer()
            if not message["params"]["request"]["url"].lower().startswith("data:"):
                self.send_request_to_amqp(message)
            else:
                self.logger.debug("ignoring data url {}".format(message["params"]["request"]["url"][:80]))
        elif "method" in message and message["method"] == "Page.loadEventFired":
            self.logger.debug("Page.loadEventFired, starting behaviors url={} message={}".format(self.url, message))
            behaviors.execute(self.url, websock, self.command_id)
        elif "method" in message and message["method"] == "Console.messageAdded":
            self.logger.debug("{} console {} {}".format(websock.url,
                message["params"]["message"]["level"],
                message["params"]["message"]["text"]))

class Umbra:
    logger = logging.getLogger('umbra.Umbra')

    def __init__(self, amqp_url, chrome_exe, browser_wait):
        self.amqp_url = amqp_url
        self.chrome_exe = chrome_exe
        self.browser_wait = browser_wait
        self.producer = None
        self.producer_lock = None
        self.workers = {}
        self.workers_lock = threading.Lock()
        self.amqp_thread = threading.Thread(target=self.consume_amqp)
        self.amqp_stop = threading.Event()
        self.amqp_thread.start()

    def shutdown(self):
        self.logger.info("shutting down amqp consumer {}".format(self.amqp_url))
        self.amqp_stop.set()
        self.amqp_thread.join()

    def consume_amqp(self):
        while not self.amqp_stop.is_set():
            try:
                self.umbra_exchange = Exchange(name='umbra', type='direct', durable=True)
                url_queue = Queue('urls', routing_key='url', exchange=self.umbra_exchange)
                self.logger.info("connecting to amqp exchange={} at {}".format(self.umbra_exchange.name, self.amqp_url))
                with Connection(self.amqp_url) as conn:
                    if self.producer_lock is None:
                        self.producer_lock = threading.Lock()
                    with self.producer_lock:
                        self.producer = conn.Producer(serializer='json')
                    with conn.Consumer(url_queue, callbacks=[self.fetch_url]) as consumer:
                        import socket
                        while not self.amqp_stop.is_set():
                            try:
                                conn.drain_events(timeout=0.5)
                            except socket.timeout:
                                pass
            except BaseException as e:
                self.logger.error("amqp exception {}".format(e))
                self.logger.error("attempting to reopen amqp connection")

    def fetch_url(self, body, message):
        client_id = body['clientId']
        with self.workers_lock:
            if not client_id in self.workers:
                port = 9222 + len(self.workers)
                t = UmbraWorker(umbra=self, chrome_port=port,
                        chrome_exe=self.chrome_exe,
                        chrome_wait=self.browser_wait,
                        client_id=client_id)
                self.workers[client_id] = t

        def browse_page_async():
            self.logger.info('client_id={} body={}'.format(client_id, body))
            self.workers[client_id].browse_page(body['url'], body['metadata'])

        threading.Thread(target=browse_page_async).start()

        message.ack()

class Chrome:
    logger = logging.getLogger('umbra.Chrome')

    def __init__(self, port, executable, browser_wait, user_data_dir):
        self.port = port
        self.executable = executable
        self.browser_wait = browser_wait
        self.user_data_dir = user_data_dir

    def fetch_debugging_json():
        raw_json = urllib.request.urlopen("http://localhost:%s/json" % self.port).read()
        json = raw_json.decode('utf-8')
        return loads(json)

    # returns websocket url to chrome window with about:blank loaded
    def __enter__(self):
        chrome_args = [self.executable,
                "--user-data-dir={}".format(self.user_data_dir),
                "--remote-debugging-port=%s" % self.port,
                "--disable-web-sockets", "--disable-cache",
                "--window-size=1100,900", "--enable-logging",
                "--homepage=about:blank", "about:blank"]
        self.logger.info("running {}".format(chrome_args))
        self.chrome_process = subprocess.Popen(chrome_args, start_new_session=True)
        self.logger.info("chrome running, pid {}".format(self.chrome_process.pid))
        start = time.time()

        json_url = "http://localhost:%s/json" % self.port

        while True:
            try:
                raw_json = urllib.request.urlopen(json_url).read()
                all_debug_info = loads(raw_json.decode('utf-8'))
                debug_info = [x for x in all_debug_info if x['url'] == 'about:blank']

                if debug_info and 'webSocketDebuggerUrl' in debug_info[0]:
                    self.logger.debug("{} returned {}".format(json_url, raw_json))
                    url = debug_info[0]['webSocketDebuggerUrl']
                    self.logger.info('got chrome window websocket debug url {} from {}'.format(url, json_url))
                    return url
            except:
                pass
            finally:
                if time.time() - start > float(self.browser_wait):
                    raise Exception("failed to retrieve {} after {} seconds".format(json_url, time.time() - start))
                else:
                    time.sleep(0.5)

    def __exit__(self, *args):
        self.logger.info("killing chrome pid {}".format(self.chrome_process.pid))
        os.killpg(self.chrome_process.pid, signal.SIGINT)
        self.chrome_process.wait()

def main():
    import faulthandler
    faulthandler.register(signal.SIGQUIT)

    arg_parser = argparse.ArgumentParser(prog=os.path.basename(sys.argv[0]),
            description='umbra - Browser automation tool',
            formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    arg_parser.add_argument('-w', '--browser-wait', dest='browser_wait', default='10',
            help='Seconds to wait for browser initialization')
    arg_parser.add_argument('-e', '--executable', dest='executable', default='chromium-browser',
            help='Executable to use to invoke chrome')
    arg_parser.add_argument('-u', '--url', dest='amqp_url', default='amqp://guest:guest@localhost:5672/%2f',
            help='URL identifying the amqp server to talk to')
    args = arg_parser.parse_args(args=sys.argv[1:])

    umbra = Umbra(args.amqp_url, args.executable, args.browser_wait)
    try:
        while True:
            time.sleep(0.5)
    except:
        pass
    finally:
        umbra.shutdown()

if __name__ == "__main__":
    main()

<|endoftext|>


<commit_before>#! /usr/bin/python
#--------------------------------------------------------------------
# PROGRAM    : __main__.py
# CREATED BY : hjkim @IIS.2015-07-13 15:46:30.658570
# MODIFED BY :
#
# USAGE      : $ ./__main__.py
#
# DESCRIPTION:
#------------------------------------------------------cf0.2@20120401

import  os,sys
from    optparse        import OptionParser
from    alien.LOGGER    import *

from    datetime        import datetime, timedelta

from    gpm             import GPM

import pylab as pl
import numpy as np
from mpl_toolkits.basemap   import Basemap

@ETA
def main(args,opts):
    print args
    print opts

    '''
    prjName = 'GPM.KuPR'
    prdLv   = 'L2'
    prdVer  = '04'
    varName = 'NS/SLV/precipRateESurface'
    '''
    prjName = 'GPM.GMI'
    prdLv   = 'L2'
    prdVer  = '03'
    varName = 'S1/surfacePrecipitation'

    #BBox    = [[20,0],[48,180]]   # Radar-AMeDAS domain
    BBox    = [[20,118],[48,150]]   # Radar-AMeDAS domain
    res     = 0.1
    delT    = timedelta(seconds=60*60)

#    sDTime  = datetime( 2014,4,30 )
#    eDTime  = datetime( 2014,5,3 )
    sDTime  = datetime( 2014,4,3,22 )
    sDTime  = datetime( 2014,4,4,0 )
    eDTime  = datetime( 2014,4,4,2 )

    print sDTime, eDTime

    gpm     = GPM(prjName, prdLv, prdVer)

    JP      = gpm(varName,
                  sDTime,
                  eDTime,
                  BBox,
                  #[[30,125],[45,145]],
                  0.2,
                  )
    '''
                  0.1,
                  timedelta(seconds=3600*24))
    '''

    sys.exit()

    A   = np.ma.masked_less_equal( np.array( JP.griddata ), 0 )

    M   = Basemap( resolution='c' ,llcrnrlat=BBox[0][0], llcrnrlon=BBox[0][1], urcrnrlat=BBox[1][0], urcrnrlon=BBox[1][1])

    pl.figure();M.imshow( A.mean(0) );pl.colorbar()
    #figure();M.scatter(JP.lon.flatten(), JP.lat.flatten(), 10, JP.data.flatten(), edgecolor='none',vmin=0,vmax=10);colorbar()
    M.drawcoastlines()
    pl.show()

    sys.exit()
    gpmJP   = gpm('NS/SLV/precipRateESurface', sDTime, eDTime, BBox, res, delT )

    A   = array( gpmJP.griddata )

    '''
    # for no delT
    Lon     = gpmJP.lon
    Lat     = gpmJP.lat
    Data    = gpmJP.data

    M.scatter(Lon.flatten(), Lat.flatten(), 10, Data.flatten(), edgecolor='none',vmin=0,vmax=10);colorbar()
    M.drawcoastlines()
    '''

    # for with delT
    Lon     = gpmJP.lon
    Lat     = gpmJP.lat
    Data    = gpmJP.data

    for lat, lon, data in zip(Lat, Lon, Data):
        figure()
        M.scatter(lon.flatten(), lat.flatten(), 10, data.flatten(), edgecolor='none',vmin=0,vmax=10);colorbar()
        M.drawcoastlines()

    show()

    print A.shape, A.max(), A.min()
    for a in A: print a.shape, a.max(), a.min()
    print gpmJP.data.min(), gpmJP.data.max()

    for a in gpmJP.griddata:
        print a.shape
        figure()
        M.imshow( ma.masked_equal(array(a), -9999.9),vmin=0,vmax=10);colorbar()
        M.drawcoastlines()

    figure()
    M.imshow( ma.masked_equal(A, -9999.9).sum(0),vmin=0,vmax=10);colorbar()
    M.drawcoastlines()

    show()

    '''
    Path    = gpm.get_path(sDTime, eDTime)

    for i in range(5):

        s=time.time()
        DTime, Lat, Lon     = gpm.get_gtrack(Path[i])

        print time.time()-s
    '''

    return

if __name__=='__main__':
    usage   = 'usage: %prog [options] arg'
    version = '%prog 1.0'

    parser  = OptionParser(usage=usage,version=version)

#    parser.add_option('-r','--rescan',action='store_true',dest='rescan',
#                      help='rescan all directory to find missing file')

    (options,args)  = parser.parse_args()

#    if len(args) == 0:
#        parser.print_help()
#    else:
#        main(args,options)

#    LOG     = LOGGER()
    main(args,options)

<commit_msg>[ADD] a use case over JP domain<commit_after>#! /usr/bin/python
#--------------------------------------------------------------------
# PROGRAM    : __main__.py
# CREATED BY : hjkim @IIS.2015-07-13 15:46:30.658570
# MODIFED BY :
#
# USAGE      : $ ./__main__.py
#
# DESCRIPTION:
#------------------------------------------------------cf0.2@20120401

import  os,sys
from    optparse        import OptionParser
from    alien.LOGGER    import *

from    datetime        import datetime, timedelta

from    gpm             import GPM

import pylab as pl
import numpy as np
from mpl_toolkits.basemap   import Basemap

@ETA
def main(args,opts):
    print args
    print opts

    '''
    prjName = 'GPM.KuPR'
    prdLv   = 'L2'
    prdVer  = '04'
    varName = 'NS/SLV/precipRateESurface'
    '''
    prjName = 'GPM.GMI'
    prdLv   = 'L2'
    prdVer  = '03'
    varName = 'S1/surfacePrecipitation'

    #BBox    = [[20,0],[48,180]]   # Radar-AMeDAS domain
    BBox    = [[20,118],[48,150]]   # Radar-AMeDAS domain
    res     = 0.1
    delT    = timedelta(seconds=60*60)

#    sDTime  = datetime( 2014,4,30 )
#    eDTime  = datetime( 2014,5,3 )
    sDTime  = datetime( 2014,4,3,22 )
    sDTime  = datetime( 2014,4,4,0 )
    eDTime  = datetime( 2014,4,5,0 )

    print sDTime, eDTime

    gpm     = GPM(prjName, prdLv, prdVer)

    JP      = gpm(varName,
                  sDTime,
                  eDTime,
                  BBox,
                  #[[30,125],[45,145]],
                  0.2,
                  )
    '''
                  0.1,
                  timedelta(seconds=3600*24))
    '''

    for d in JP.griddata:
        print d.shape, d.max(), d.min(), np.ma.masked_less_equal(d,0).sum()
    #sys.exit()

    H   = np.arange(25)
    for h0,h1 in zip(H[:-1], H[1:]):
        offset = 0 if h1 !=24 else 1

        sdtime = datetime(2014,4,4,h0)
        edtime = datetime(2014,4,4 + offset,h1%24)

        gpmJP   = gpm(varName, sdtime, edtime, BBox, 0.2 )

        if hasattr( gpmJP, 'griddata'):
            for d in gpmJP.griddata:
                print d.shape, d.max(), d.min(), np.ma.masked_less_equal(d,0).sum()

    sys.exit()

    A   = np.ma.masked_less_equal( np.array( JP.griddata ), 0 )

    M   = Basemap( resolution='c' ,llcrnrlat=BBox[0][0], llcrnrlon=BBox[0][1], urcrnrlat=BBox[1][0], urcrnrlon=BBox[1][1])

    pl.figure();M.imshow( A.mean(0) );pl.colorbar()
    #figure();M.scatter(JP.lon.flatten(), JP.lat.flatten(), 10, JP.data.flatten(), edgecolor='none',vmin=0,vmax=10);colorbar()
    M.drawcoastlines()
    pl.show()

    sys.exit()
    gpmJP   = gpm('NS/SLV/precipRateESurface', sDTime, eDTime, BBox, res, delT )

    A   = array( gpmJP.griddata )

    '''
    # for no delT
    Lon     = gpmJP.lon
    Lat     = gpmJP.lat
    Data    = gpmJP.data

    M.scatter(Lon.flatten(), Lat.flatten(), 10, Data.flatten(), edgecolor='none',vmin=0,vmax=10);colorbar()
    M.drawcoastlines()
    '''

    # for with delT
    Lon     = gpmJP.lon
    Lat     = gpmJP.lat
    Data    = gpmJP.data

    for lat, lon, data in zip(Lat, Lon, Data):
        figure()
        M.scatter(lon.flatten(), lat.flatten(), 10, data.flatten(), edgecolor='none',vmin=0,vmax=10);colorbar()
        M.drawcoastlines()

    show()

    print A.shape, A.max(), A.min()
    for a in A: print a.shape, a.max(), a.min()
    print gpmJP.data.min(), gpmJP.data.max()

    for a in gpmJP.griddata:
        print a.shape
        figure()
        M.imshow( ma.masked_equal(array(a), -9999.9),vmin=0,vmax=10);colorbar()
        M.drawcoastlines()

    figure()
    M.imshow( ma.masked_equal(A, -9999.9).sum(0),vmin=0,vmax=10);colorbar()
    M.drawcoastlines()

    show()

    '''
    Path    = gpm.get_path(sDTime, eDTime)

    for i in range(5):

        s=time.time()
        DTime, Lat, Lon     = gpm.get_gtrack(Path[i])

        print time.time()-s
    '''

    return

if __name__=='__main__':
    usage   = 'usage: %prog [options] arg'
    version = '%prog 1.0'

    parser  = OptionParser(usage=usage,version=version)

#    parser.add_option('-r','--rescan',action='store_true',dest='rescan',
#                      help='rescan all directory to find missing file')

    (options,args)  = parser.parse_args()

#    if len(args) == 0:
#        parser.print_help()
#    else:
#        main(args,options)

#    LOG     = LOGGER()
    main(args,options)

<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Copyright 2016 Ionuț Arțăriși <ionut@artarisi.eu>

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Return a function with any decorators removed """

__version__ = '0.1.1'

def undecorated(o):
    """Remove all decorators from a function, method or class"""
    # class decorator
    if type(o) is type:
        return o

    try:
        # python2
        closure = o.func_closure
    except AttributeError:
        pass

    try:
        # python3
        closure = o.__closure__
    except AttributeError:
        return

    if closure:
        for cell in closure:
            # avoid infinite recursion
            if cell.cell_contents is o:
                continue

            undecd = undecorated(cell.cell_contents)
            if undecd:
                return undecd
    else:
        return o
<commit_msg>Remove module docstring as we have it on the func<commit_after># -*- coding: utf-8 -*-
# Copyright 2016 Ionuț Arțăriși <ionut@artarisi.eu>

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

__version__ = '0.1.1'

def undecorated(o):
    """Remove all decorators from a function, method or class"""
    # class decorator
    if type(o) is type:
        return o

    try:
        # python2
        closure = o.func_closure
    except AttributeError:
        pass

    try:
        # python3
        closure = o.__closure__
    except AttributeError:
        return

    if closure:
        for cell in closure:
            # avoid infinite recursion
            if cell.cell_contents is o:
                continue

            undecd = undecorated(cell.cell_contents)
            if undecd:
                return undecd
    else:
        return o
<|endoftext|>


<commit_before>"""Paster Commands, for use with paster in your project

The command(s) listed here are for use with Paste to enable easy creation of
various core Pylons templates.

Currently available commands are::

    controller, shell
"""
import os
import sys
import pylons
import pylons.util as util

from paste.script.command import Command, BadCommand
from paste.script.filemaker import FileOp
from paste.deploy import loadapp, appconfig
import paste.deploy.config
import paste.fixture

def validate_name(name):
    """Validate that the name for the controller isn't present on the
    path already"""
    if not name:
        # This happens when the name is an existing directory
        raise BadCommand('Please give the name of a controller.')
    try:
        __import__(name)
        raise BadCommand(
            "\n\nA module named '%s' is already present in your "
            "PYTHON_PATH.\n Choosing a conflicting name will likely cause"
            " import problems in\n your controller at some point. It's "
            "suggested that you choose an alternate\nname, and if you'd "
            "like that name to be accessible as '%s', add a route\n"
            "to your projects config/routing.py file similar to:\n"
            "  map.connect('%s', controller='my_%s')" \
            % (name, name, name, name))
    except ImportError:
        # This is actually the result we want
        pass

    return True

class ControllerCommand(Command):
    """Create a Controller and functional test for it

    The Controller command will create the standard controller template
    file and associated functional test to speed creation of controllers.

    Example usage::

        yourproj% paster controller comments
        Creating yourproj/yourproj/controllers/comments.py
        Creating yourproj/yourproj/tests/functional/test_comments.py

    If you'd like to have controllers underneath a directory, just include
    the path as the controller name and the necessary directories will be
    created for you::

        yourproj% paster controller admin/trackback
        Creating yourproj/controllers/admin
        Creating yourproj/yourproj/controllers/admin/trackback.py
        Creating yourproj/yourproj/tests/functional/test_admin_trackback.py
    """
    summary = __doc__
    usage = 'CONTROLLER_NAME'

    min_args = 1
    max_args = 1
    group_name = 'pylons'

    default_verbosity = 3

    parser = Command.standard_parser(simulate=True)
    parser.add_option('--no-test',
                      action='store_true',
                      dest='no_test',
                      help="Don't create the test; just the controller")

    def command(self):
        """Main command to create controller"""
        try:
            file_op = FileOp(source_dir=os.path.join(
                os.path.dirname(__file__), 'templates'))
            try:
                name, directory = file_op.parse_path_name_args(self.args[0])
            except:
                raise BadCommand('No egg_info directory was found')

            # Check the name isn't the same as the package
            base_package, cdir = file_op.find_dir('controllers', True)
            if base_package.lower() == name.lower():
                raise BadCommand(
                    'Your controller name should not be the same as '
                    'the package name %r.'% base_package
            )
            # Validate the name
            name = name.replace('-', '_')
            validate_name(name)

            # Setup the controller
            fullname = os.path.join(directory, name)
            controller_name = util.class_name_from_module_name(
                name.split('/')[-1])
            if not fullname.startswith(os.sep):
                fullname = os.sep + fullname
            testname = fullname.replace(os.sep, '_')[1:]
            file_op.template_vars.update({'name': controller_name,
                                  'fname': os.path.join(directory, name)})
            file_op.copy_file(template='controller.py_tmpl',
                         dest=os.path.join('controllers', directory),
                         filename=name)
            if not self.options.no_test:
                file_op.copy_file(template='test_controller.py_tmpl',
                             dest=os.path.join('tests', 'functional'),
                             filename='test_'+testname)
        except BadCommand, e:
            raise BadCommand('An error occurred. %s' % e)
        except:
            msg = str(sys.exc_info()[1])
            raise BadCommand('An unknown error occurred. %s' % msg)

class ShellCommand(Command):
    """Open an interactive shell with the Pylons app loaded

    The optional CONFIG_FILE argument specifies the config file to use for
    the interactive shell. CONFIG_FILE defaults to 'development.ini'.

    This allows you to test your mapper, models, and simulate web requests
    using ``paste.fixture``.

    Example::

        $ paster shell my-development.ini
    """
    summary = __doc__
    usage = '[CONFIG_FILE]'

    min_args = 0
    max_args = 1
    group_name = 'pylons'

    parser = Command.standard_parser(simulate=True)

    def command(self):
        """Main command to create a new shell"""
        self.verbose = 3
        if len(self.args) == 0:
            # Assume the .ini file is ./development.ini
            config_file = 'development.ini'
            if not os.path.isfile(config_file):
                raise BadCommand('%sError: CONFIG_FILE not found at: .%s%s\n'
                                 'Please specify a CONFIG_FILE' % \
                                 (self.parser.get_usage(), os.path.sep,
                                  config_file))
        else:
            config_file = self.args[0]

        config_name = 'config:%s' % config_file
        here_dir = os.getcwd()
        locs = dict(__name__="pylons-admin")
        pkg_name = here_dir.split(os.path.sep)[-1].lower()

        # Load app config into paste.deploy to simulate request config
        app_conf = appconfig(config_name, relative_to=here_dir)
        conf = dict(app=app_conf, app_conf=app_conf)
        paste.deploy.config.CONFIG.push_thread_config(conf)

        # Load locals and populate with objects for use in shell
        sys.path.insert(0, here_dir)

        # Load the wsgi app first so that everything is initialized right
        wsgiapp = loadapp(config_name, relative_to=here_dir)
        test_app = app=paste.fixture.TestApp(wsgiapp)

        # Start the rest of our imports now that the app is loaded
        models_package = pkg_name + '.models'
        __import__(models_package)

        tresponse = test_app.get('/_test_vars')

        locs.update(
            dict(
                model=sys.modules[models_package],
                mapper=tresponse.pylons_config.map,
                wsgiapp=wsgiapp,
                app=test_app,
                h=tresponse.h,
                g=tresponse.g,
            )
        )
        pylons.g._push_object(tresponse.g)

        banner = "Pylons Interactive Shell\nPython %s\n\n" % sys.version
        banner += "Additional Objects:\n"
        banner += "  %-10s -  %s\n" % ('mapper', 'Routes mapper object')
        banner += "  %-10s -  %s\n" % ('h', 'Helper object')
        banner += "  %-10s -  %s\n" % ('g', 'Globals object')
        banner += "  %-10s -  %s\n" % ('model', 'Models from models package')
        banner += "  %-10s -  %s\n" % ('wsgiapp',
            'This projects WSGI App instance')
        banner += "  %-10s -  %s\n" % ('app',
            'paste.fixture wrapped around wsgiapp')
        try:
            # try to use IPython if possible
            import IPython

            class CustomIPShell(IPython.iplib.InteractiveShell):
                """Custom shell class to handle raw input"""
                def raw_input(self, *args, **kw):
                    """Capture raw input in exception wrapping"""
                    try:
                        return IPython.iplib.InteractiveShell.raw_input(
                            self, *args, **kw)
                    except EOFError:
                        # In the future, we'll put our own override as needed
                        # to save models, TG style
                        raise EOFError

            shell = IPython.Shell.IPShell(user_ns=locs,
                shell_class=CustomIPShell)
            shell.mainloop()
        except ImportError:
            import code

            class CustomShell(code.InteractiveConsole):
                """Custom shell class to handle raw input"""
                def raw_input(self, *args, **kw):
                    """Capture raw input in exception wrapping"""
                    try:
                        return code.InteractiveConsole.raw_input(
                            self, *args, **kw)
                    except EOFError:
                        # In the future, we'll put our own override as needed
                        # to save models, TG style
                        raise EOFError

            shell = CustomShell(locals=locs)
            try:
                import readline
            except ImportError:
                pass
            shell.interact(banner)
<commit_msg>[svn] summarizing the command summaries<commit_after>"""Paster Commands, for use with paster in your project

The command(s) listed here are for use with Paste to enable easy creation of
various core Pylons templates.

Currently available commands are::

    controller, shell
"""
import os
import sys
import pylons
import pylons.util as util

from paste.script.command import Command, BadCommand
from paste.script.filemaker import FileOp
from paste.deploy import loadapp, appconfig
import paste.deploy.config
import paste.fixture

def validate_name(name):
    """Validate that the name for the controller isn't present on the
    path already"""
    if not name:
        # This happens when the name is an existing directory
        raise BadCommand('Please give the name of a controller.')
    try:
        __import__(name)
        raise BadCommand(
            "\n\nA module named '%s' is already present in your "
            "PYTHON_PATH.\n Choosing a conflicting name will likely cause"
            " import problems in\n your controller at some point. It's "
            "suggested that you choose an alternate\nname, and if you'd "
            "like that name to be accessible as '%s', add a route\n"
            "to your projects config/routing.py file similar to:\n"
            "  map.connect('%s', controller='my_%s')" \
            % (name, name, name, name))
    except ImportError:
        # This is actually the result we want
        pass

    return True

class ControllerCommand(Command):
    """Create a Controller and accompanying functional test

    The Controller command will create the standard controller template
    file and associated functional test to speed creation of controllers.

    Example usage::

        yourproj% paster controller comments
        Creating yourproj/yourproj/controllers/comments.py
        Creating yourproj/yourproj/tests/functional/test_comments.py

    If you'd like to have controllers underneath a directory, just include
    the path as the controller name and the necessary directories will be
    created for you::

        yourproj% paster controller admin/trackback
        Creating yourproj/controllers/admin
        Creating yourproj/yourproj/controllers/admin/trackback.py
        Creating yourproj/yourproj/tests/functional/test_admin_trackback.py
    """
    summary = __doc__.splitlines()[0]
    usage = 'CONTROLLER_NAME'

    min_args = 1
    max_args = 1
    group_name = 'pylons'

    default_verbosity = 3

    parser = Command.standard_parser(simulate=True)
    parser.add_option('--no-test',
                      action='store_true',
                      dest='no_test',
                      help="Don't create the test; just the controller")

    def command(self):
        """Main command to create controller"""
        try:
            file_op = FileOp(source_dir=os.path.join(
                os.path.dirname(__file__), 'templates'))
            try:
                name, directory = file_op.parse_path_name_args(self.args[0])
            except:
                raise BadCommand('No egg_info directory was found')

            # Check the name isn't the same as the package
            base_package, cdir = file_op.find_dir('controllers', True)
            if base_package.lower() == name.lower():
                raise BadCommand(
                    'Your controller name should not be the same as '
                    'the package name %r.'% base_package
            )
            # Validate the name
            name = name.replace('-', '_')
            validate_name(name)

            # Setup the controller
            fullname = os.path.join(directory, name)
            controller_name = util.class_name_from_module_name(
                name.split('/')[-1])
            if not fullname.startswith(os.sep):
                fullname = os.sep + fullname
            testname = fullname.replace(os.sep, '_')[1:]
            file_op.template_vars.update({'name': controller_name,
                                  'fname': os.path.join(directory, name)})
            file_op.copy_file(template='controller.py_tmpl',
                         dest=os.path.join('controllers', directory),
                         filename=name)
            if not self.options.no_test:
                file_op.copy_file(template='test_controller.py_tmpl',
                             dest=os.path.join('tests', 'functional'),
                             filename='test_'+testname)
        except BadCommand, e:
            raise BadCommand('An error occurred. %s' % e)
        except:
            msg = str(sys.exc_info()[1])
            raise BadCommand('An unknown error occurred. %s' % msg)

class ShellCommand(Command):
    """Open an interactive shell with the Pylons app loaded

    The optional CONFIG_FILE argument specifies the config file to use for
    the interactive shell. CONFIG_FILE defaults to 'development.ini'.

    This allows you to test your mapper, models, and simulate web requests
    using ``paste.fixture``.

    Example::

        $ paster shell my-development.ini
    """
    summary = __doc__.splitlines()[0]
    usage = '[CONFIG_FILE]'

    min_args = 0
    max_args = 1
    group_name = 'pylons'

    parser = Command.standard_parser(simulate=True)

    def command(self):
        """Main command to create a new shell"""
        self.verbose = 3
        if len(self.args) == 0:
            # Assume the .ini file is ./development.ini
            config_file = 'development.ini'
            if not os.path.isfile(config_file):
                raise BadCommand('%sError: CONFIG_FILE not found at: .%s%s\n'
                                 'Please specify a CONFIG_FILE' % \
                                 (self.parser.get_usage(), os.path.sep,
                                  config_file))
        else:
            config_file = self.args[0]

        config_name = 'config:%s' % config_file
        here_dir = os.getcwd()
        locs = dict(__name__="pylons-admin")
        pkg_name = here_dir.split(os.path.sep)[-1].lower()

        # Load app config into paste.deploy to simulate request config
        app_conf = appconfig(config_name, relative_to=here_dir)
        conf = dict(app=app_conf, app_conf=app_conf)
        paste.deploy.config.CONFIG.push_thread_config(conf)

        # Load locals and populate with objects for use in shell
        sys.path.insert(0, here_dir)

        # Load the wsgi app first so that everything is initialized right
        wsgiapp = loadapp(config_name, relative_to=here_dir)
        test_app = app=paste.fixture.TestApp(wsgiapp)

        # Start the rest of our imports now that the app is loaded
        models_package = pkg_name + '.models'
        __import__(models_package)

        tresponse = test_app.get('/_test_vars')

        locs.update(
            dict(
                model=sys.modules[models_package],
                mapper=tresponse.pylons_config.map,
                wsgiapp=wsgiapp,
                app=test_app,
                h=tresponse.h,
                g=tresponse.g,
            )
        )
        pylons.g._push_object(tresponse.g)

        banner = "Pylons Interactive Shell\nPython %s\n\n" % sys.version
        banner += "Additional Objects:\n"
        banner += "  %-10s -  %s\n" % ('mapper', 'Routes mapper object')
        banner += "  %-10s -  %s\n" % ('h', 'Helper object')
        banner += "  %-10s -  %s\n" % ('g', 'Globals object')
        banner += "  %-10s -  %s\n" % ('model', 'Models from models package')
        banner += "  %-10s -  %s\n" % ('wsgiapp',
            'This projects WSGI App instance')
        banner += "  %-10s -  %s\n" % ('app',
            'paste.fixture wrapped around wsgiapp')
        try:
            # try to use IPython if possible
            import IPython

            class CustomIPShell(IPython.iplib.InteractiveShell):
                """Custom shell class to handle raw input"""
                def raw_input(self, *args, **kw):
                    """Capture raw input in exception wrapping"""
                    try:
                        return IPython.iplib.InteractiveShell.raw_input(
                            self, *args, **kw)
                    except EOFError:
                        # In the future, we'll put our own override as needed
                        # to save models, TG style
                        raise EOFError

            shell = IPython.Shell.IPShell(user_ns=locs,
                shell_class=CustomIPShell)
            shell.mainloop()
        except ImportError:
            import code

            class CustomShell(code.InteractiveConsole):
                """Custom shell class to handle raw input"""
                def raw_input(self, *args, **kw):
                    """Capture raw input in exception wrapping"""
                    try:
                        return code.InteractiveConsole.raw_input(
                            self, *args, **kw)
                    except EOFError:
                        # In the future, we'll put our own override as needed
                        # to save models, TG style
                        raise EOFError

            shell = CustomShell(locals=locs)
            try:
                import readline
            except ImportError:
                pass
            shell.interact(banner)
<|endoftext|>


<commit_before>#  Copyright 2008-2011 Nokia Siemens Networks Oyj
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

class JsonWriter(object):

    def __init__(self, output, separator=''):
        self._writer = JsonDumper(output)
        self._separator = separator

    def write_json(self, prefix, data, postfix=';\n', mapping=None, separator=False):
        self._writer.write(prefix)
        self._writer.dump(data, mapping)
        self._writer.write(postfix)
        if separator:
            self.separator()

    def write(self, string, separator=False):
        self._writer.write(string)
        if separator:
            self.separator()

    def separator(self):
        self._writer.write(self._separator)

class JsonDumper(object):

    def __init__(self, output):
        self._output = output
        self._dumpers = (_MappingDumper(self),
                         _IntegerDumper(self),
                         _TupleListDumper(self),
                         _NoneDumper(self),
                         _DictDumper(self),
                         _StringDumper(self))

    def dump(self, data, mapping=None):
        for dumper in self._dumpers:
            dumper.add_mapping(mapping)
            if dumper.handles(data):
                dumper.dump(data)
                return
        raise ValueError('Dumping %s not supported' % type(data))

    def write(self, data):
        self._output.write(data)

class _DataDumper(object):

    def __init__(self, jsondumper):
        self._jsondumper = jsondumper
        self._mapping = {}

    def add_mapping(self, mapping):
        self._mapping = mapping or {}

    def _dump(self, data):
        self._jsondumper.dump(data, self._mapping)

    def _write(self, data):
        self._jsondumper.write(data)

class _StringDumper(_DataDumper):
    _replace = [('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'),
                ('\n', '\\n'), ('\r', '\\r')]

    def handles(self, data):
        return isinstance(data, basestring)

    def dump(self, string):
        for char, repl in self._replace:
            string = string.replace(char, repl)
        self._write('"%s"' % ''.join(self._encode_char(c) for c in string))

    def _encode_char(self, char):
        val = ord(char)
        if 31 < val < 127:
            return char
        return '\\u' + hex(val)[2:].rjust(4, '0')

class _IntegerDumper(_DataDumper):

    def handles(self, data):
        return isinstance(data, (int, long))

    def dump(self, data):
        self._write(str(data))

class _DictDumper(_DataDumper):

    def handles(self, data):
        return isinstance(data, dict)

    def dump(self, data):
        self._write('{')
        last_index = len(data) - 1
        for index, key in enumerate(sorted(data)):
            self._dump(key)
            self._write(':')
            self._dump(data[key])
            if index < last_index:
                self._write(',')
        self._write('}')

class _TupleListDumper(_DataDumper):

    def handles(self, data):
        return isinstance(data, (tuple, list))

    def dump(self, data):
        self._write('[')
        last_index = len(data) - 1
        for index, item in enumerate(data):
            self._dump(item)
            if index < last_index:
                self._write(',')
        self._write(']')

class _MappingDumper(_DataDumper):

    def handles(self, data):
        try:
            return data in self._mapping
        except TypeError:
            return False

    def dump(self, data):
        self._write(self._mapping[data])

class _NoneDumper(_DataDumper):

    def handles(self, data):
        return data is None

    def dump(self, data):
        self._write('null')
<commit_msg>JsonDump: more straightforward handling of mappings<commit_after>#  Copyright 2008-2011 Nokia Siemens Networks Oyj
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

class JsonWriter(object):

    def __init__(self, output, separator=''):
        self._writer = JsonDumper(output)
        self._separator = separator

    def write_json(self, prefix, data, postfix=';\n', mapping=None, separator=False):
        self._writer.write(prefix)
        self._writer.dump(data, mapping)
        self._writer.write(postfix)
        if separator:
            self.separator()

    def write(self, string, separator=False):
        self._writer.write(string)
        if separator:
            self.separator()

    def separator(self):
        self._writer.write(self._separator)

class JsonDumper(object):

    def __init__(self, output):
        self._output = output
        self._dumpers = (_MappingDumper(self),
                         _IntegerDumper(self),
                         _TupleListDumper(self),
                         _NoneDumper(self),
                         _DictDumper(self),
                         _StringDumper(self))

    def dump(self, data, mapping=None):
        for dumper in self._dumpers:
            if dumper.handles(data, mapping):
                dumper.dump(data, mapping)
                return
        raise ValueError('Dumping %s not supported' % type(data))

    def write(self, data):
        self._output.write(data)

class _DataDumper(object):
    _handled_types = None

    def __init__(self, jsondumper):
        self._dump = jsondumper.dump
        self._write = jsondumper.write

    def handles(self, data, mapping):
        return isinstance(data, self._handled_types)

    def dump(self, data, mapping):
        raise NotImplementedError

class _StringDumper(_DataDumper):
    _handled_types = basestring
    _replace = (('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'),
                ('\n', '\\n'), ('\r', '\\r'))

    def dump(self, data, mapping):
        for char, repl in self._replace:
            data = data.replace(char, repl)
        self._write('"%s"' % ''.join(self._encode_char(c) for c in data))

    def _encode_char(self, char):
        val = ord(char)
        if 31 < val < 127:
            return char
        return '\\u' + hex(val)[2:].rjust(4, '0')

class _IntegerDumper(_DataDumper):
    _handled_types = (int, long)

    def dump(self, data, mapping):
        self._write(str(data))

class _DictDumper(_DataDumper):
    _handled_types = dict

    def dump(self, data, mapping):
        self._write('{')
        last_index = len(data) - 1
        for index, key in enumerate(sorted(data)):
            self._dump(key, mapping)
            self._write(':')
            self._dump(data[key], mapping)
            if index < last_index:
                self._write(',')
        self._write('}')

class _TupleListDumper(_DataDumper):
    _handled_types = (tuple, list)

    def dump(self, data, mapping):
        self._write('[')
        last_index = len(data) - 1
        for index, item in enumerate(data):
            self._dump(item, mapping)
            if index < last_index:
                self._write(',')
        self._write(']')

class _MappingDumper(_DataDumper):

    def handles(self, data, mapping):
        try:
            return mapping and data in mapping
        except TypeError:
            return False

    def dump(self, data, mapping):
        self._write(mapping[data])

class _NoneDumper(_DataDumper):

    def handles(self, data, mapping):
        return data is None

    def dump(self, data, mapping):
        self._write('null')
<|endoftext|>


<commit_before>"""Paster Commands, for use with paster in your project

The command(s) listed here are for use with Paste to enable easy creation of
various core Pylons templates.

Currently available commands are::

    controller, shell
"""
import os
import sys
import pylons
import pylons.util as util

from paste.script.command import Command, BadCommand
from paste.script.filemaker import FileOp
from paste.deploy import loadapp, appconfig
import paste.deploy.config
import paste.fixture

def validate_name(name):
    """Validate that the name for the controller isn't present on the
    path already"""
    if not name:
        # This happens when the name is an existing directory
        raise BadCommand('Please give the name of a controller.')
    try:
        __import__(name)
        raise BadCommand(
            "\n\nA module named '%s' is already present in your "
            "PYTHON_PATH.\n Choosing a conflicting name will likely cause"
            " import problems in\n your controller at some point. It's "
            "suggested that you choose an alternate\nname, and if you'd "
            "like that name to be accessible as '%s', add a route\n"
            "to your projects config/routing.py file similar to:\n"
            "  map.connect('%s', controller='my_%s')" \
            % (name, name, name, name))
    except ImportError:
        # This is actually the result we want
        pass

    return True

class ControllerCommand(Command):
    """Create a Controller and functional test for it

    The Controller command will create the standard controller template
    file and associated functional test to speed creation of controllers.

    Example usage::

        yourproj% paster controller comments
        Creating yourproj/yourproj/controllers/comments.py
        Creating yourproj/yourproj/tests/functional/test_comments.py

    If you'd like to have controllers underneath a directory, just include
    the path as the controller name and the necessary directories will be
    created for you::

        yourproj% paster controller admin/trackback
        Creating yourproj/controllers/admin
        Creating yourproj/yourproj/controllers/admin/trackback.py
        Creating yourproj/yourproj/tests/functional/test_admin_trackback.py
    """
    summary = __doc__
    usage = 'CONTROLLER_NAME'

    min_args = 1
    max_args = 1
    group_name = 'pylons'

    default_verbosity = 3

    parser = Command.standard_parser(simulate=True)
    parser.add_option('--no-test',
                      action='store_true',
                      dest='no_test',
                      help="Don't create the test; just the controller")

    def command(self):
        """Main command to create controller"""
        try:
            file_op = FileOp(source_dir=os.path.join(
                os.path.dirname(__file__), 'templates'))
            try:
                name, directory = file_op.parse_path_name_args(self.args[0])
            except:
                raise BadCommand('No egg_info directory was found')

            # Validate the name
            name = name.replace('-', '_')
            validate_name(name)

            fullname = os.path.join(directory, name)
            controller_name = util.class_name_from_module_name(
                name.split('/')[-1])
            if not fullname.startswith(os.sep):
                fullname = os.sep + fullname
            testname = fullname.replace(os.sep, '_')[1:]
            file_op.template_vars.update({'name': controller_name,
                                  'fname': os.path.join(directory, name)})
            file_op.copy_file(template='controller.py_tmpl',
                         dest=os.path.join('controllers', directory),
                         filename=name)
            if not self.options.no_test:
                file_op.copy_file(template='test_controller.py_tmpl',
                             dest=os.path.join('tests', 'functional'),
                             filename='test_'+testname)
        except:
            msg = str(sys.exc_info()[1])
            raise BadCommand('An unknown error ocurred, %s' % msg)

class ShellCommand(Command):
    """Open an interactive shell with the Pylons app loaded

    The optional CONFIG_FILE argument specifies the config file to use for
    the interactive shell. CONFIG_FILE defaults to 'development.ini'.

    This allows you to test your mapper, models, and simulate web requests
    using ``paste.fixture``.

    Example::

        $ paster shell my-development.ini
    """
    summary = __doc__
    usage = '[CONFIG_FILE]'

    min_args = 0
    max_args = 1
    group_name = 'pylons'

    parser = Command.standard_parser(simulate=True)

    def command(self):
        """Main command to create a new shell"""
        self.verbose = 3
        if len(self.args) == 0:
            # Assume the .ini file is ./development.ini
            config_file = 'development.ini'
            if not os.path.isfile(config_file):
                raise BadCommand('%sError: CONFIG_FILE not found at: .%s%s\n'
                                 'Please specify a CONFIG_FILE' % \
                                 (self.parser.get_usage(), os.path.sep,
                                  config_file))
        else:
            config_file = self.args[0]

        config_name = 'config:%s' % config_file
        here_dir = os.getcwd()
        locs = dict(__name__="pylons-admin")
        pkg_name = here_dir.split(os.path.sep)[-1].lower()

        # Load app config into paste.deploy to simulate request config
        app_conf = appconfig(config_name, relative_to=here_dir)
        conf = dict(app=app_conf, app_conf=app_conf)
        paste.deploy.config.CONFIG.push_thread_config(conf)

        # Load locals and populate with objects for use in shell
        sys.path.insert(0, here_dir)

        # Load the wsgi app first so that everything is initialized right
        wsgiapp = loadapp(config_name, relative_to=here_dir)
        test_app = app=paste.fixture.TestApp(wsgiapp)

        # Start the rest of our imports now that the app is loaded
        models_package = pkg_name + '.models'
        __import__(models_package)

        tresponse = test_app.get('/_test_vars')

        locs.update(
            dict(
                model=sys.modules[models_package],
                mapper=tresponse.pylons_config.map,
                wsgiapp=wsgiapp,
                app=test_app,
                h=tresponse.h,
                g=tresponse.g,
            )
        )
        pylons.g.push_object(tresponse.g)

        banner = "Pylons Interactive Shell\nPython %s\n\n" % sys.version
        banner += "Additional Objects:\n"
        banner += "  %-10s -  %s\n" % ('mapper', 'Routes mapper object')
        banner += "  %-10s -  %s\n" % ('h', 'Helper object')
        banner += "  %-10s -  %s\n" % ('g', 'Globals object')
        banner += "  %-10s -  %s\n" % ('model', 'Models from models package')
        banner += "  %-10s -  %s\n" % ('wsgiapp',
            'This projects WSGI App instance')
        banner += "  %-10s -  %s\n" % ('app',
            'paste.fixture wrapped around wsgiapp')
        try:
            # try to use IPython if possible
            import IPython

            class CustomIPShell(IPython.iplib.InteractiveShell):
                """Custom shell class to handle raw input"""
                def raw_input(self, *args, **kw):
                    """Capture raw input in exception wrapping"""
                    try:
                        return IPython.iplib.InteractiveShell.raw_input(
                            self, *args, **kw)
                    except EOFError:
                        # In the future, we'll put our own override as needed
                        # to save models, TG style
                        raise EOFError

            shell = IPython.Shell.IPShell(user_ns=locs,
                shell_class=CustomIPShell)
            shell.mainloop()
        except ImportError:
            import code

            class CustomShell(code.InteractiveConsole):
                """Custom shell class to handle raw input"""
                def raw_input(self, *args, **kw):
                    """Capture raw input in exception wrapping"""
                    try:
                        return code.InteractiveConsole.raw_input(
                            self, *args, **kw)
                    except EOFError:
                        # In the future, we'll put our own override as needed
                        # to save models, TG style
                        raise EOFError

            shell = CustomShell(locals=locs)
            try:
                import readline
            except ImportError:
                pass
            shell.interact(banner)
<commit_msg>[svn] Added a check that the controller name isn't the same as the package name. Fixes #143<commit_after>"""Paster Commands, for use with paster in your project

The command(s) listed here are for use with Paste to enable easy creation of
various core Pylons templates.

Currently available commands are::

    controller, shell
"""
import os
import sys
import pylons
import pylons.util as util

from paste.script.command import Command, BadCommand
from paste.script.filemaker import FileOp
from paste.deploy import loadapp, appconfig
import paste.deploy.config
import paste.fixture

def validate_name(name):
    """Validate that the name for the controller isn't present on the
    path already"""
    if not name:
        # This happens when the name is an existing directory
        raise BadCommand('Please give the name of a controller.')
    try:
        __import__(name)
        raise BadCommand(
            "\n\nA module named '%s' is already present in your "
            "PYTHON_PATH.\n Choosing a conflicting name will likely cause"
            " import problems in\n your controller at some point. It's "
            "suggested that you choose an alternate\nname, and if you'd "
            "like that name to be accessible as '%s', add a route\n"
            "to your projects config/routing.py file similar to:\n"
            "  map.connect('%s', controller='my_%s')" \
            % (name, name, name, name))
    except ImportError:
        # This is actually the result we want
        pass

    return True

class ControllerCommand(Command):
    """Create a Controller and functional test for it

    The Controller command will create the standard controller template
    file and associated functional test to speed creation of controllers.

    Example usage::

        yourproj% paster controller comments
        Creating yourproj/yourproj/controllers/comments.py
        Creating yourproj/yourproj/tests/functional/test_comments.py

    If you'd like to have controllers underneath a directory, just include
    the path as the controller name and the necessary directories will be
    created for you::

        yourproj% paster controller admin/trackback
        Creating yourproj/controllers/admin
        Creating yourproj/yourproj/controllers/admin/trackback.py
        Creating yourproj/yourproj/tests/functional/test_admin_trackback.py
    """
    summary = __doc__
    usage = 'CONTROLLER_NAME'

    min_args = 1
    max_args = 1
    group_name = 'pylons'

    default_verbosity = 3

    parser = Command.standard_parser(simulate=True)
    parser.add_option('--no-test',
                      action='store_true',
                      dest='no_test',
                      help="Don't create the test; just the controller")

    def command(self):
        """Main command to create controller"""
        try:
            file_op = FileOp(source_dir=os.path.join(
                os.path.dirname(__file__), 'templates'))
            try:
                name, directory = file_op.parse_path_name_args(self.args[0])
            except:
                raise BadCommand('No egg_info directory was found')

            # Check the name isn't the same as the package
            base_package, cdir = file_op.find_dir('controllers', True)
            if base_package.lower() == name.lower():
                raise BadCommand(
                    'Your controller name should not be the same as '
                    'the package name %r.'% base_package
            )
            # Validate the name
            name = name.replace('-', '_')
            validate_name(name)

            # Setup the controller
            fullname = os.path.join(directory, name)
            controller_name = util.class_name_from_module_name(
                name.split('/')[-1])
            if not fullname.startswith(os.sep):
                fullname = os.sep + fullname
            testname = fullname.replace(os.sep, '_')[1:]
            file_op.template_vars.update({'name': controller_name,
                                  'fname': os.path.join(directory, name)})
            file_op.copy_file(template='controller.py_tmpl',
                         dest=os.path.join('controllers', directory),
                         filename=name)
            if not self.options.no_test:
                file_op.copy_file(template='test_controller.py_tmpl',
                             dest=os.path.join('tests', 'functional'),
                             filename='test_'+testname)
        except BadCommand, e:
            raise BadCommand('An error ocurred. %s' % e)
        except:
            msg = str(sys.exc_info()[1])
            raise BadCommand('An unknown error ocurred. %s' % msg)

class ShellCommand(Command):
    """Open an interactive shell with the Pylons app loaded

    The optional CONFIG_FILE argument specifies the config file to use for
    the interactive shell. CONFIG_FILE defaults to 'development.ini'.

    This allows you to test your mapper, models, and simulate web requests
    using ``paste.fixture``.

    Example::

        $ paster shell my-development.ini
    """
    summary = __doc__
    usage = '[CONFIG_FILE]'

    min_args = 0
    max_args = 1
    group_name = 'pylons'

    parser = Command.standard_parser(simulate=True)

    def command(self):
        """Main command to create a new shell"""
        self.verbose = 3
        if len(self.args) == 0:
            # Assume the .ini file is ./development.ini
            config_file = 'development.ini'
            if not os.path.isfile(config_file):
                raise BadCommand('%sError: CONFIG_FILE not found at: .%s%s\n'
                                 'Please specify a CONFIG_FILE' % \
                                 (self.parser.get_usage(), os.path.sep,
                                  config_file))
        else:
            config_file = self.args[0]

        config_name = 'config:%s' % config_file
        here_dir = os.getcwd()
        locs = dict(__name__="pylons-admin")
        pkg_name = here_dir.split(os.path.sep)[-1].lower()

        # Load app config into paste.deploy to simulate request config
        app_conf = appconfig(config_name, relative_to=here_dir)
        conf = dict(app=app_conf, app_conf=app_conf)
        paste.deploy.config.CONFIG.push_thread_config(conf)

        # Load locals and populate with objects for use in shell
        sys.path.insert(0, here_dir)

        # Load the wsgi app first so that everything is initialized right
        wsgiapp = loadapp(config_name, relative_to=here_dir)
        test_app = app=paste.fixture.TestApp(wsgiapp)

        # Start the rest of our imports now that the app is loaded
        models_package = pkg_name + '.models'
        __import__(models_package)

        tresponse = test_app.get('/_test_vars')

        locs.update(
            dict(
                model=sys.modules[models_package],
                mapper=tresponse.pylons_config.map,
                wsgiapp=wsgiapp,
                app=test_app,
                h=tresponse.h,
                g=tresponse.g,
            )
        )
        pylons.g.push_object(tresponse.g)

        banner = "Pylons Interactive Shell\nPython %s\n\n" % sys.version
        banner += "Additional Objects:\n"
        banner += "  %-10s -  %s\n" % ('mapper', 'Routes mapper object')
        banner += "  %-10s -  %s\n" % ('h', 'Helper object')
        banner += "  %-10s -  %s\n" % ('g', 'Globals object')
        banner += "  %-10s -  %s\n" % ('model', 'Models from models package')
        banner += "  %-10s -  %s\n" % ('wsgiapp',
            'This projects WSGI App instance')
        banner += "  %-10s -  %s\n" % ('app',
            'paste.fixture wrapped around wsgiapp')
        try:
            # try to use IPython if possible
            import IPython

            class CustomIPShell(IPython.iplib.InteractiveShell):
                """Custom shell class to handle raw input"""
                def raw_input(self, *args, **kw):
                    """Capture raw input in exception wrapping"""
                    try:
                        return IPython.iplib.InteractiveShell.raw_input(
                            self, *args, **kw)
                    except EOFError:
                        # In the future, we'll put our own override as needed
                        # to save models, TG style
                        raise EOFError

            shell = IPython.Shell.IPShell(user_ns=locs,
                shell_class=CustomIPShell)
            shell.mainloop()
        except ImportError:
            import code

            class CustomShell(code.InteractiveConsole):
                """Custom shell class to handle raw input"""
                def raw_input(self, *args, **kw):
                    """Capture raw input in exception wrapping"""
                    try:
                        return code.InteractiveConsole.raw_input(
                            self, *args, **kw)
                    except EOFError:
                        # In the future, we'll put our own override as needed
                        # to save models, TG style
                        raise EOFError

            shell = CustomShell(locals=locs)
            try:
                import readline
            except ImportError:
                pass
            shell.interact(banner)
<|endoftext|>


<commit_before>#  Copyright 2008-2009 Nokia Siemens Networks Oyj
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import wx
from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin

from robotide.utils import ButtonWithHandler, RideEventHandler
from robotide.widgets import PopupMenu, PopupMenuItems
from robotide.context.platform import ctrl_or_cmd, bind_keys_to_evt_menu

class ListEditorBase(wx.Panel):
    _menu = ['Edit', 'Move Up\tCtrl-Up', 'Move Down\tCtrl-Down', '---', 'Delete']
    _buttons = []

    def __init__(self, parent, columns, controller):
        wx.Panel.__init__(self, parent)
        self._controller = controller
        self._selection = -1
        self._create_ui(columns, controller)
        self._make_bindings()
        self._bind_keys()

    def _bind_keys(self):
        bind_keys_to_evt_menu(self, self._get_bind_keys())

    def _get_bind_keys(self):
        return [(ctrl_or_cmd(), wx.WXK_UP, self.OnMoveUp),
                    (ctrl_or_cmd(), wx.WXK_DOWN, self.OnMoveDown),
                    (wx.ACCEL_NORMAL, wx.WXK_WINDOWS_MENU, self.OnRightClick)]

    def _create_ui(self, columns, data):
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        self._list = self._create_list(columns, data)
        sizer.Add(self._list, 1, wx.EXPAND)
        sizer.Add((5,0))
        sizer.Add(self._create_buttons())
        sizer.Add((5,0))
        self.SetSizer(sizer)
        sizer.Layout()

    def _create_list(self, columns, data):
        return AutoWidthColumnList(self, columns, data)

    def _create_buttons(self):
        sizer = wx.BoxSizer(wx.VERTICAL)
        for label in self._buttons:
            sizer.Add(ButtonWithHandler(self, label, width=120), 0, wx.ALL, 1)
        return sizer

    def _make_bindings(self):
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnEdit)
        self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnRightClick)

    def OnRightClick(self, event):
        PopupMenu(self, PopupMenuItems(self, self._menu))

    def OnItemSelected(self, event):
        self._selection = event.GetIndex()

    def OnItemActivated(self, event):
        pass

    def OnMoveUp(self, event):
        if self._selection < 1:
            return
        self._controller.move_up(self._selection)
        self.update_data()
        self._list.Select(self._selection-1, True)

    def OnMoveDown(self, event):
        if self._selection == self._list.GetItemCount() - 1 or self._selection == -1:
            return
        self._controller.move_down(self._selection)
        self.update_data()
        self._list.Select(self._selection+1, True)

    def OnDelete(self, event):
        if self._selection == -1:
            return
        self._controller.delete(self._selection)
        self.update_data()
        item_count = self._list.GetItemCount()
        if self._selection >= item_count:
            self._selection = item_count - 1
        self._list.Select(self._selection, True)

    def update_data(self):
        self._list.DeleteAllItems()
        self._list.insert_data(self._controller)

    def update_selected_item(self, data):
        self._list.update_item(self._selection, data)

    def select(self, text):
        self._list.select(text)

class ListEditor(ListEditorBase, RideEventHandler): pass

class AutoWidthColumnList(wx.ListCtrl, ListCtrlAutoWidthMixin):

    def __init__(self, parent, columns, data=[]):
        wx.ListCtrl.__init__(self, parent,
                             style=wx.LC_REPORT|wx.NO_BORDER|wx.LC_SINGLE_SEL|wx.LC_HRULES)
        ListCtrlAutoWidthMixin.__init__(self)
        self._parent = parent
        self.populate(columns, data)

    def populate(self, columns, data):
        for i, name in enumerate(columns):
            self.InsertColumn(i, name)
        self.insert_data(data)

    def insert_data(self, data):
        for row, item in enumerate(data):
            rowdata = self._parent.get_column_values(item)
            self.InsertStringItem(row, rowdata[0])
            for i in range(1, len(rowdata)):
                data = rowdata[i] is not None and rowdata[i] or ''
                self.SetStringItem(row, i, data)
        for i in range(self.ColumnCount):
            self.SetColumnWidth(i, -1)
            if self.GetColumnWidth(i) < 120:
                self.SetColumnWidth(i, 120)
            if self.GetColumnWidth(i) > 350:
                self.SetColumnWidth(i, 350)

    def update_item(self, index, data):
        self.SetItemText(index, data[0])
        for col in range(1, len(data)):
            self.SetStringItem(index, col, data[col])

    def select(self, text):
        item = self.FindItem(0, text)
        if item >= 0:
            self.Select(item)
            # FIXME: EnsureVisible does not seem to work
            self.EnsureVisible(item)
<commit_msg>listeditor: Now that editors are properly reused, also the autoscroll works, so the FIXME no longer applies<commit_after>#  Copyright 2008-2009 Nokia Siemens Networks Oyj
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import wx
from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin

from robotide.utils import ButtonWithHandler, RideEventHandler
from robotide.widgets import PopupMenu, PopupMenuItems
from robotide.context.platform import ctrl_or_cmd, bind_keys_to_evt_menu

class ListEditorBase(wx.Panel):
    _menu = ['Edit', 'Move Up\tCtrl-Up', 'Move Down\tCtrl-Down', '---', 'Delete']
    _buttons = []

    def __init__(self, parent, columns, controller):
        wx.Panel.__init__(self, parent)
        self._controller = controller
        self._selection = -1
        self._create_ui(columns, controller)
        self._make_bindings()
        self._bind_keys()

    def _bind_keys(self):
        bind_keys_to_evt_menu(self, self._get_bind_keys())

    def _get_bind_keys(self):
        return [(ctrl_or_cmd(), wx.WXK_UP, self.OnMoveUp),
                    (ctrl_or_cmd(), wx.WXK_DOWN, self.OnMoveDown),
                    (wx.ACCEL_NORMAL, wx.WXK_WINDOWS_MENU, self.OnRightClick)]

    def _create_ui(self, columns, data):
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        self._list = self._create_list(columns, data)
        sizer.Add(self._list, 1, wx.EXPAND)
        sizer.Add((5,0))
        sizer.Add(self._create_buttons())
        sizer.Add((5,0))
        self.SetSizer(sizer)
        sizer.Layout()

    def _create_list(self, columns, data):
        return AutoWidthColumnList(self, columns, data)

    def _create_buttons(self):
        sizer = wx.BoxSizer(wx.VERTICAL)
        for label in self._buttons:
            sizer.Add(ButtonWithHandler(self, label, width=120), 0, wx.ALL, 1)
        return sizer

    def _make_bindings(self):
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnEdit)
        self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnRightClick)

    def OnRightClick(self, event):
        PopupMenu(self, PopupMenuItems(self, self._menu))

    def OnItemSelected(self, event):
        self._selection = event.GetIndex()

    def OnItemActivated(self, event):
        pass

    def OnMoveUp(self, event):
        if self._selection < 1:
            return
        self._controller.move_up(self._selection)
        self.update_data()
        self._list.Select(self._selection-1, True)

    def OnMoveDown(self, event):
        if self._selection == self._list.GetItemCount() - 1 or self._selection == -1:
            return
        self._controller.move_down(self._selection)
        self.update_data()
        self._list.Select(self._selection+1, True)

    def OnDelete(self, event):
        if self._selection == -1:
            return
        self._controller.delete(self._selection)
        self.update_data()
        item_count = self._list.GetItemCount()
        if self._selection >= item_count:
            self._selection = item_count - 1
        self._list.Select(self._selection, True)

    def update_data(self):
        self._list.DeleteAllItems()
        self._list.insert_data(self._controller)

    def update_selected_item(self, data):
        self._list.update_item(self._selection, data)

    def select(self, text):
        self._list.select(text)

class ListEditor(ListEditorBase, RideEventHandler): pass

class AutoWidthColumnList(wx.ListCtrl, ListCtrlAutoWidthMixin):

    def __init__(self, parent, columns, data=[]):
        wx.ListCtrl.__init__(self, parent,
                             style=wx.LC_REPORT|wx.NO_BORDER|wx.LC_SINGLE_SEL|wx.LC_HRULES)
        ListCtrlAutoWidthMixin.__init__(self)
        self._parent = parent
        self.populate(columns, data)

    def populate(self, columns, data):
        for i, name in enumerate(columns):
            self.InsertColumn(i, name)
        self.insert_data(data)

    def insert_data(self, data):
        for row, item in enumerate(data):
            rowdata = self._parent.get_column_values(item)
            self.InsertStringItem(row, rowdata[0])
            for i in range(1, len(rowdata)):
                data = rowdata[i] is not None and rowdata[i] or ''
                self.SetStringItem(row, i, data)
        for i in range(self.ColumnCount):
            self.SetColumnWidth(i, -1)
            if self.GetColumnWidth(i) < 120:
                self.SetColumnWidth(i, 120)
            if self.GetColumnWidth(i) > 350:
                self.SetColumnWidth(i, 350)

    def update_item(self, index, data):
        self.SetItemText(index, data[0])
        for col in range(1, len(data)):
            self.SetStringItem(index, col, data[col])

    def select(self, text):
        item = self.FindItem(0, text)
        if item >= 0:
            self.Select(item)
            self.EnsureVisible(item)
<|endoftext|>