<commit_before>"""Contains the base Data class."""

from .exceptions import DuplicateXError

class Data:
    """Represents a list of measurements.

    :param \*values: The values that make up the data."""

    def __init__(self, *values, name="y", xname="x"):
        try:
            if str in [type(v) for v in values]: raise TypeError
            self._values = [[index, value] for index, value in values]
            if len(set([v[0] for v in values])) != len([v[0] for v in values]):
                raise DuplicateXError(
                 "{} contains duplicate x values".format(values)
                )
        except ValueError:
            raise ValueError("Iterable values can only be of length 2")
        except TypeError:
            self._values = [[index, value] for index, value in enumerate(values)]
        if not isinstance(name, str):
            raise TypeError("name '{}' is not a str".format(name))
        self._name = name
        if not isinstance(xname, str):
            raise TypeError("xname '{}' is not a str".format(xname))
        self._xname = xname

    def __repr__(self):
        if self._name == "y":
            return "<Data {}>".format(tuple([val[1] for val in self._values]))
        return "<Data '{}' {}>".format(
         self._name, tuple([val[1] for val in self._values])
        )

    def __len__(self):
        return len(self._values)

    def __contains__(self, member):
        for x, y in self._values:
            if y == member: return True
        return False

    def __iter__(self):
        return iter([val[1] for val in self._values])

    def __getitem__(self, key):
        for x, y in self._values:
            if x == key: return y
        raise IndexError("{} has no x value {}".format(self, key))

    def __setitem__(self, key, y):
        for index, value in enumerate(self._values):
            if value[0] == key:
                self._values[index][1] = y
                return
        self._values.append([key, y])

    def __delitem__(self, key):
        for index, value in enumerate(self._values):
            if value[0] == key:
                self._values.pop(index)
                return
<commit_msg>Add docstring for Data<commit_after>"""Contains the base Data class."""

from .exceptions import DuplicateXError

class Data:
    """Represents a list of measurements, as x-value, y-value pairs. Generally
    this class would not be instantiated directly, but is here to act as a
    parent class for others.

    You can provide a list (or tuple, or set - any collection other than string)
    of values, and these will become the y values of the data, with the input x
    values being 0-based indices. Alternatively, you can provide a list of
    length-2 lists (or tuples etc.) and *these* will be interpreted as xy pairs.

    A ``Data`` object is a container of its y values, is iterable (stepping
    through it will step through its y values), and its x values are accessed
    using square brackets like a dictionary, with the corresponding x-value
    being the key.

    You can delete a value using ``del data[x_value]`` notation.

    :param \*values: The values that make up the data.
    :param str name: The name of the y-values.
    :param str xname: The name of the x-values."""

    def __init__(self, *values, name="y", xname="x"):
        try:
            if str in [type(v) for v in values]: raise TypeError
            self._values = [[index, value] for index, value in values]
            if len(set([v[0] for v in values])) != len([v[0] for v in values]):
                raise DuplicateXError(
                 "{} contains duplicate x values".format(values)
                )
        except ValueError:
            raise ValueError("Iterable values can only be of length 2")
        except TypeError:
            self._values = [[index, value] for index, value in enumerate(values)]
        if not isinstance(name, str):
            raise TypeError("name '{}' is not a str".format(name))
        self._name = name
        if not isinstance(xname, str):
            raise TypeError("xname '{}' is not a str".format(xname))
        self._xname = xname

    def __repr__(self):
        if self._name == "y":
            return "<Data {}>".format(tuple([val[1] for val in self._values]))
        return "<Data '{}' {}>".format(
         self._name, tuple([val[1] for val in self._values])
        )

    def __len__(self):
        return len(self._values)

    def __contains__(self, member):
        for x, y in self._values:
            if y == member: return True
        return False

    def __iter__(self):
        return iter([val[1] for val in self._values])

    def __getitem__(self, key):
        for x, y in self._values:
            if x == key: return y
        raise IndexError("{} has no x value {}".format(self, key))

    def __setitem__(self, key, y):
        for index, value in enumerate(self._values):
            if value[0] == key:
                self._values[index][1] = y
                return
        self._values.append([key, y])

    def __delitem__(self, key):
        for index, value in enumerate(self._values):
            if value[0] == key:
                self._values.pop(index)
                return
<|endoftext|>


<commit_before>import threading
import importlib
import atexit
from time import sleep
import Queue
from helpers import read_config

listener = None

class InputListener():
    """A class which listens for input device events and calls according callbacks if set"""
    listening = False
    keymap = {}
    stop_flag = False

    def __init__(self, drivers, keymap={}):
        """Init function for creating KeyListener object. Checks all the arguments and sets keymap if supplied."""
        self.drivers = drivers
        self.queue = Queue.Queue()
        for driver, _ in self.drivers:
            driver.send_key = self.receive_key #Overriding the send_key method so that keycodes get sent to InputListener
        self.set_keymap(keymap)

    def receive_key(self, key):
        """ This is the method that receives keypresses from drivers and puts them into ``self.queue`` for ``self.event_loop`` to receive """
        try:
            self.queue.put(key)
        except:
            raise #Just collecting possible exceptions for now

    def set_callback(self, key_name, callback):
        """Sets a single callback of the listener"""
        self.keymap[key_name] = callback

    def remove_callback(self, key_name):
        """Removes a single callback of the listener"""
        self.keymap.remove(key_name)

    def set_keymap(self, keymap):
        """Sets all the callbacks supplied, removing the previously set keymap completely"""
        self.keymap = keymap

    def replace_keymap_entries(self, keymap):
        """Sets all the callbacks supplied, not removing previously set but overwriting those with same keycodes"""
        for key in keymap.keys:
            set_callback(key, keymap[key])

    def clear_keymap(self):
        """Removes all the callbacks set"""
        self.keymap = {}

    def event_loop(self):
        """Blocking event loop which just calls callbacks in the keymap once corresponding keys are received in the ``self.queue``."""
        self.listening = True
        try:
            while not self.stop_flag:
                try:
                    key = self.queue.get(False, 0.1)
                except Queue.Empty:
                    sleep(0.1)
                except AttributeError:
                    print("Heh, caught that.") #Typically gets printed if InputListener exits abnormally upon program termination
                else:
                    self.process_key(key)
        except:
            raise
        finally:
            self.listening = False

    def process_key(self, key):
        if key in self.keymap:
            callback = self.keymap[key]
            try:
                callback()
            except Exception as e:
                self.handle_callback_exception(key, callback, e)
            finally:
                return

    def handle_callback_exception(self, key, callback, e):
        print("Exception caused by callback {} when key {} was received".format(callback, key))
        print("Exception: {}".format(e))
        #raise e
        import pdb;pdb.set_trace()

    def listen(self):
        """Start  event_loop in a thread. Nonblocking."""
        self.stop_flag = False
        for driver, _ in self.drivers:
            driver.start()
        self.listener_thread = threading.Thread(target = self.event_loop)
        self.listener_thread.daemon = False
        self.listener_thread.start()
        return True

    def stop_listen(self):
        """This sets a flag for ``event_loop`` to stop. It also calls a ``stop`` method of the input driver ``InputListener`` is using."""
        self.stop_flag = True
        for driver, _ in self.drivers:
            driver.stop()
        return True

    def atexit(self):
        """Exits driver (if necessary) if something wrong happened or pyLCI exits. Also, stops the listener"""
        self.stop_listen()
        for driver, _ in self.drivers:
            if hasattr(driver, "atexit"):
                driver.atexit()
        try:
            self.listener_thread.join()
        except AttributeError:
            pass

def init():
    """ This function is called by main.py to read the input configuration, pick the corresponding drivers and initialize InputListener.

    It also sets ``listener`` globals of ``input`` module with driver and listener respectively, as well as registers ``listener.stop()`` function to be called when script exits since it's in a blocking non-daemon thread."""
    global listener
    config = read_config("config.json")
    input_configs = config["input"]
    drivers = []
    for input_config in input_configs:
        driver_name = input_config["driver"]
        driver_module = importlib.import_module("input.drivers."+driver_name)
        args = input_config["args"] if "args" in input_config else []
        kwargs = input_config["kwargs"] if "kwargs" in input_config else {}
        driver = driver_module.InputDevice(*args, **kwargs)
        drivers.append([driver, driver_name])
    listener = InputListener(drivers)
    atexit.register(listener.atexit)
<commit_msg>Better wording<commit_after>import threading
import importlib
import atexit
from time import sleep
import Queue
from helpers import read_config

listener = None

class InputListener():
    """A class which listens for input device events and calls corresponding callbacks if set"""
    listening = False
    keymap = {}
    stop_flag = False

    def __init__(self, drivers, keymap={}):
        """Init function for creating KeyListener object. Checks all the arguments and sets keymap if supplied."""
        self.drivers = drivers
        self.queue = Queue.Queue()
        for driver, _ in self.drivers:
            driver.send_key = self.receive_key #Overriding the send_key method so that keycodes get sent to InputListener
        self.set_keymap(keymap)

    def receive_key(self, key):
        """ This is the method that receives keypresses from drivers and puts them into ``self.queue`` for ``self.event_loop`` to receive """
        try:
            self.queue.put(key)
        except:
            raise #Just collecting possible exceptions for now

    def set_callback(self, key_name, callback):
        """Sets a single callback of the listener"""
        self.keymap[key_name] = callback

    def remove_callback(self, key_name):
        """Removes a single callback of the listener"""
        self.keymap.remove(key_name)

    def set_keymap(self, keymap):
        """Sets all the callbacks supplied, removing the previously set keymap completely"""
        self.keymap = keymap

    def replace_keymap_entries(self, keymap):
        """Sets all the callbacks supplied, not removing previously set but overwriting those with same keycodes"""
        for key in keymap.keys:
            set_callback(key, keymap[key])

    def clear_keymap(self):
        """Removes all the callbacks set"""
        self.keymap = {}

    def event_loop(self):
        """Blocking event loop which just calls callbacks in the keymap once corresponding keys are received in the ``self.queue``."""
        self.listening = True
        try:
            while not self.stop_flag:
                try:
                    key = self.queue.get(False, 0.1)
                except Queue.Empty:
                    sleep(0.1)
                except AttributeError:
                    print("Heh, caught that.") #Typically gets printed if InputListener exits abnormally upon program termination
                else:
                    self.process_key(key)
        except:
            raise
        finally:
            self.listening = False

    def process_key(self, key):
        if key in self.keymap:
            callback = self.keymap[key]
            try:
                callback()
            except Exception as e:
                self.handle_callback_exception(key, callback, e)
            finally:
                return

    def handle_callback_exception(self, key, callback, e):
        print("Exception caused by callback {} when key {} was received".format(callback, key))
        print("Exception: {}".format(e))
        #raise e
        import pdb;pdb.set_trace()

    def listen(self):
        """Start  event_loop in a thread. Nonblocking."""
        self.stop_flag = False
        for driver, _ in self.drivers:
            driver.start()
        self.listener_thread = threading.Thread(target = self.event_loop)
        self.listener_thread.daemon = False
        self.listener_thread.start()
        return True

    def stop_listen(self):
        """This sets a flag for ``event_loop`` to stop. It also calls a ``stop`` method of the input driver ``InputListener`` is using."""
        self.stop_flag = True
        for driver, _ in self.drivers:
            driver.stop()
        return True

    def atexit(self):
        """Exits driver (if necessary) if something wrong happened or pyLCI exits. Also, stops the listener"""
        self.stop_listen()
        for driver, _ in self.drivers:
            if hasattr(driver, "atexit"):
                driver.atexit()
        try:
            self.listener_thread.join()
        except AttributeError:
            pass

def init():
    """ This function is called by main.py to read the input configuration, pick the corresponding drivers and initialize InputListener.

    It also sets ``listener`` globals of ``input`` module with driver and listener respectively, as well as registers ``listener.stop()`` function to be called when script exits since it's in a blocking non-daemon thread."""
    global listener
    config = read_config("config.json")
    input_configs = config["input"]
    drivers = []
    for input_config in input_configs:
        driver_name = input_config["driver"]
        driver_module = importlib.import_module("input.drivers."+driver_name)
        args = input_config["args"] if "args" in input_config else []
        kwargs = input_config["kwargs"] if "kwargs" in input_config else {}
        driver = driver_module.InputDevice(*args, **kwargs)
        drivers.append([driver, driver_name])
    listener = InputListener(drivers)
    atexit.register(listener.atexit)
<|endoftext|>


<commit_before>import sublime
import sublime_plugin

try:
    from .format_date import FormatDate, UnknownTimeZoneError  # ST3
except ValueError:
    from format_date import FormatDate, UnknownTimeZoneError  # ST2

ST2 = int(sublime.version()) < 3000

if not ST2:
    basestring = str

# Global variables
fdate = FormatDate()
# Global settings object
s = None
# Print tracebacks
DEBUG = False

def status(msg, e=None):
    msg = "[InsertDate] " + msg
    sublime.status_message(msg)
    if e is not None:
        msg += "\n%s: %s" % (type(e).__name__, e)
    print(msg)

    if e and DEBUG:
        import traceback
        traceback.print_exc()

# I wrote this for InactivePanes, but why not just use it here as well?
class Settings(object):
    """Provides various helping functions for wrapping the sublime settings objects.

    `settings` should be provided as a dict of tuples and attribute names should not be one of the
    existing functions. And of course they should be valid attribute names.

    Example constructor:
    Settings(
        sublime.load_settings("Preferences.sublime-settings"),
        dict(
            attr_name_to_save_as=('settings_key_to_read_from', 'default_value'),
            attr_name_to_save_as2='settings_key_to_read_from_with_default_None',
            attr_name_and_settings_key_with_default_None=None
            #, ...
        ),
        on_settings_changed,  # optional, callback
        auto_update  # optional, bool
                     # (whether the attributes should be kept up to date; default: True)
    )

    `settings_changed` will be called when the registered settings changed, and this time for real.
    Sublime Text currently behaves weird with `add_on_change` calls and the callback is run more
    often than it should be (as in, the specified setting didn't actually change), this wrapper
    however tests if one of the values has changed and then calls the callback.
    `update()` is called before the callback.

    Methods:
        * update()
            Reads all the settings and saves them in their respective attributes.
        * has_changed()
            Returns a boolean if the currently cached settings differ.
        * get_state()
            Returns a dict with the tracked settings as keys and their values (NOT the attribute
            names). With the above example: `{"settings_key_to_read_from": 'current_value'}`.
        * get_real_state()
            Same as above but ALWAYS returns the actual current values in the settings object.
        * set_callback(callback, auto_update=True)
            Calls `callback` whenever a tracked setting's value changes. See above on why this
            behavior differs to `register`.
            If `auto_update` is true it will automatically update the attributes when the settings
            changes. This is always true when a callback is set.
        * clear_callback(clear_auto_update=False)
            Clears the callback set above and returns it in the process.
    """
    _sobj = None
    _settings = None
    _callback = None
    _auto_update = False

    def __init__(self, settings_obj, settings, callback=None, auto_update=True):
        self._sobj = settings_obj

        for k, v in settings.items():
            if v is None:
                # Use the attr name as settings key and `None` as default
                settings[k] = (k, None)
            if isinstance(v, basestring):
                # Set default to `None` if a string was supplied
                settings[k] = (v, None)
        self._settings = settings

        self.update()
        self.set_callback(callback, auto_update)

    def update(self):
        for attr, (name, def_value) in self._settings.items():
            setattr(self, attr, self._sobj.get(name, def_value))

    def _on_change(self):
        # Only trigger if relevant settings changed
        if self.has_changed():
            self.update()
            if self._callback:
                self._callback()

    def _register(self, callback):
        for name, _ in self._settings.values():
            self._sobj.add_on_change(name, callback)

    def _unregister(self):
        for name, _ in self._settings.values():
            self._sobj.clear_on_change(name)

    def has_changed(self):
        return self.get_state() != self.get_real_state()

    def get_state(self):
        return dict((name, getattr(self, attr))
                    for attr, (name, _) in self._settings.items())

    def get_real_state(self):
        return dict((name, self._sobj.get(name, def_value))
                    for name, def_value in self._settings.values())

    def set_callback(self, callback, auto_update=True):
        if callback is not None and not callable(callback):
            raise TypeError("callback must be callable")

        cb = self._callback
        self._callback = callback
        if (not cb and not self._auto_update) and (callback or auto_update):
            self._register(self._on_change)

        self._auto_update = auto_update
        return cb

    def clear_callback(self, clear_auto_update=False):
        cb = self._callback
        if cb or not self._auto_update or clear_auto_update:
            self._unregister()
        self._callback = None
        return cb

# The actual commands
class InsertDateCommand(sublime_plugin.TextCommand):

    """Prints Date according to given format string."""

    def run(self, edit, format=None, tz_in=None, tz_out=None):
        if format is not None:
            if format == '' or not isinstance(format, basestring) or format.isspace():
                # Not a string, empty or only whitespaces
                return

        # Do the actual parse action
        try:
            text = fdate.parse(format, tz_in, tz_out)
        except UnknownTimeZoneError as e:
            status(str(e).strip('"'), e)
            return
        except Exception as e:
            status('Error parsing format string `%s`' % format, e)
            return

        # Don't bother replacing selections with actually nothing
        if text == '' or text.isspace():
            return

        # Do replacements
        for r in self.view.sel():
            # Insert when sel is empty to not select the contents
            if r.empty():
                self.view.insert(edit, r.a, text)
            else:
                self.view.replace(edit, r, text)

class InsertDatePromptCommand(sublime_plugin.TextCommand):

    """Ask for a format string, while preserving the other parameters.

    If "format" is provided, it will be pre-inserted into the prompt.
    """

    def run(self, edit, format=None, tz_in=None, tz_out=None):
        def on_done(fmt):
            self.view.run_command(
                "insert_date",
                {"format": fmt, "tz_in": tz_in, "tz_out": tz_out}
            )

        i_panel = self.view.window().show_input_panel(
            # Caption
            "Date format string:",
            # Default text
            format or fdate.default['format'],
            # on_done callback
            on_done,
            # Unnecessary callbacks
            None, None
        )

        # Select the default text
        i_panel.sel().clear()
        i_panel.sel().add(sublime.Region(0, i_panel.size()))

class InsertDatePanelCommand(sublime_plugin.TextCommand):

    """Shows a quick panel with configurable templates that are previewed."""

    panel_cache = []
    config_map = {}

    def run(self, edit, tz_in=None, tz_out=None):
        self.panel_cache = []
        self.config_map = {}

        if not isinstance(s.prompt_config, list):
            status("`prompt_config` setting is invalid")
            return

        configs = s.prompt_config
        if not isinstance(s.user_prompt_config, list):
            status("`user_prompt_config` setting is invalid")
        else:
            configs = configs + s.user_prompt_config

        if not configs:
            status("No configurations found to choose from")
            return

        # Generate panel cache for quick_panel
        for conf in configs:
            # Read config
            c = dict()
            c['tz_in']  = tz_in if tz_in else conf.get('tz_in')
            c['tz_out'] = tz_out if tz_out else conf.get('tz_out')
            c['format'] = conf.get('format')

            if isinstance(c['format'], basestring):
                c['format'] = c['format'].replace("$default", fdate.default['format'])

            # Do the actual parse action
            try:
                text = fdate.parse(**c)
            except UnknownTimeZoneError as e:
                status(str(e).strip('"'), e)
                return
            except Exception as e:
                status('Error parsing format string `%s`' % format, e)
                return

            self.panel_cache.append([conf['name'], text])
            self.config_map[conf['name']] = c

        self.view.window().show_quick_panel(self.panel_cache, self.on_done)

    def on_done(self, index):
        if index == -1:
            return

        name = self.panel_cache[index][0]
        self.view.run_command("insert_date", self.config_map[name])

# Handle context
def plugin_loaded():
    global s, fdate

    s = Settings(
        sublime.load_settings('insert_date.sublime-settings'),
        settings=dict(
            format=('format', '%c'),
            tz_in=('tz_in', 'local'),
            prompt_config=('prompt_config', []),
            user_prompt_config=('user_prompt_config', [])
        )
    )

    # Register on settings changes
    def on_settings_changed():
        print("InsertDate settings changed")
        # These defaults will be used when the command's parameters are None
        fdate.set_default(s.get_state())

    on_settings_changed()  # Apply initial settings
    s.set_callback(on_settings_changed)

def plugin_unloaded():
    global s

    s.clear_callback(True)

# ST2 backwards (and don't call it twice in ST3)
unload_handler = plugin_unloaded if ST2 else lambda: None

# Call manually if on ST2
if ST2:
    plugin_loaded()
<commit_msg>Don't block unloading in case something failed<commit_after>import sublime
import sublime_plugin

try:
    from .format_date import FormatDate, UnknownTimeZoneError  # ST3
except ValueError:
    from format_date import FormatDate, UnknownTimeZoneError  # ST2

ST2 = int(sublime.version()) < 3000

if not ST2:
    basestring = str

# Global variables
fdate = FormatDate()
# Global settings object
s = None
# Print tracebacks
DEBUG = False

def status(msg, e=None):
    msg = "[InsertDate] " + msg
    sublime.status_message(msg)
    if e is not None:
        msg += "\n%s: %s" % (type(e).__name__, e)
    print(msg)

    if e and DEBUG:
        import traceback
        traceback.print_exc()

# I wrote this for InactivePanes, but why not just use it here as well?
class Settings(object):
    """Provides various helping functions for wrapping the sublime settings objects.

    `settings` should be provided as a dict of tuples and attribute names should not be one of the
    existing functions. And of course they should be valid attribute names.

    Example constructor:
    Settings(
        sublime.load_settings("Preferences.sublime-settings"),
        dict(
            attr_name_to_save_as=('settings_key_to_read_from', 'default_value'),
            attr_name_to_save_as2='settings_key_to_read_from_with_default_None',
            attr_name_and_settings_key_with_default_None=None
            #, ...
        ),
        on_settings_changed,  # optional, callback
        auto_update  # optional, bool
                     # (whether the attributes should be kept up to date; default: True)
    )

    `settings_changed` will be called when the registered settings changed, and this time for real.
    Sublime Text currently behaves weird with `add_on_change` calls and the callback is run more
    often than it should be (as in, the specified setting didn't actually change), this wrapper
    however tests if one of the values has changed and then calls the callback.
    `update()` is called before the callback.

    Methods:
        * update()
            Reads all the settings and saves them in their respective attributes.
        * has_changed()
            Returns a boolean if the currently cached settings differ.
        * get_state()
            Returns a dict with the tracked settings as keys and their values (NOT the attribute
            names). With the above example: `{"settings_key_to_read_from": 'current_value'}`.
        * get_real_state()
            Same as above but ALWAYS returns the actual current values in the settings object.
        * set_callback(callback, auto_update=True)
            Calls `callback` whenever a tracked setting's value changes. See above on why this
            behavior differs to `register`.
            If `auto_update` is true it will automatically update the attributes when the settings
            changes. This is always true when a callback is set.
        * clear_callback(clear_auto_update=False)
            Clears the callback set above and returns it in the process.
    """
    _sobj = None
    _settings = None
    _callback = None
    _auto_update = False

    def __init__(self, settings_obj, settings, callback=None, auto_update=True):
        self._sobj = settings_obj

        for k, v in settings.items():
            if v is None:
                # Use the attr name as settings key and `None` as default
                settings[k] = (k, None)
            if isinstance(v, basestring):
                # Set default to `None` if a string was supplied
                settings[k] = (v, None)
        self._settings = settings

        self.update()
        self.set_callback(callback, auto_update)

    def update(self):
        for attr, (name, def_value) in self._settings.items():
            setattr(self, attr, self._sobj.get(name, def_value))

    def _on_change(self):
        # Only trigger if relevant settings changed
        if self.has_changed():
            self.update()
            if self._callback:
                self._callback()

    def _register(self, callback):
        for name, _ in self._settings.values():
            self._sobj.add_on_change(name, callback)

    def _unregister(self):
        for name, _ in self._settings.values():
            self._sobj.clear_on_change(name)

    def has_changed(self):
        return self.get_state() != self.get_real_state()

    def get_state(self):
        return dict((name, getattr(self, attr))
                    for attr, (name, _) in self._settings.items())

    def get_real_state(self):
        return dict((name, self._sobj.get(name, def_value))
                    for name, def_value in self._settings.values())

    def set_callback(self, callback, auto_update=True):
        if callback is not None and not callable(callback):
            raise TypeError("callback must be callable")

        cb = self._callback
        self._callback = callback
        if (not cb and not self._auto_update) and (callback or auto_update):
            self._register(self._on_change)

        self._auto_update = auto_update
        return cb

    def clear_callback(self, clear_auto_update=False):
        cb = self._callback
        if cb or not self._auto_update or clear_auto_update:
            self._unregister()
        self._callback = None
        return cb

# The actual commands
class InsertDateCommand(sublime_plugin.TextCommand):

    """Prints Date according to given format string."""

    def run(self, edit, format=None, tz_in=None, tz_out=None):
        if format is not None:
            if format == '' or not isinstance(format, basestring) or format.isspace():
                # Not a string, empty or only whitespaces
                return

        # Do the actual parse action
        try:
            text = fdate.parse(format, tz_in, tz_out)
        except UnknownTimeZoneError as e:
            status(str(e).strip('"'), e)
            return
        except Exception as e:
            status('Error parsing format string `%s`' % format, e)
            return

        # Don't bother replacing selections with actually nothing
        if text == '' or text.isspace():
            return

        # Do replacements
        for r in self.view.sel():
            # Insert when sel is empty to not select the contents
            if r.empty():
                self.view.insert(edit, r.a, text)
            else:
                self.view.replace(edit, r, text)

class InsertDatePromptCommand(sublime_plugin.TextCommand):

    """Ask for a format string, while preserving the other parameters.

    If "format" is provided, it will be pre-inserted into the prompt.
    """

    def run(self, edit, format=None, tz_in=None, tz_out=None):
        def on_done(fmt):
            self.view.run_command(
                "insert_date",
                {"format": fmt, "tz_in": tz_in, "tz_out": tz_out}
            )

        i_panel = self.view.window().show_input_panel(
            # Caption
            "Date format string:",
            # Default text
            format or fdate.default['format'],
            # on_done callback
            on_done,
            # Unnecessary callbacks
            None, None
        )

        # Select the default text
        i_panel.sel().clear()
        i_panel.sel().add(sublime.Region(0, i_panel.size()))

class InsertDatePanelCommand(sublime_plugin.TextCommand):

    """Shows a quick panel with configurable templates that are previewed."""

    panel_cache = []
    config_map = {}

    def run(self, edit, tz_in=None, tz_out=None):
        self.panel_cache = []
        self.config_map = {}

        if not isinstance(s.prompt_config, list):
            status("`prompt_config` setting is invalid")
            return

        configs = s.prompt_config
        if not isinstance(s.user_prompt_config, list):
            status("`user_prompt_config` setting is invalid")
        else:
            configs = configs + s.user_prompt_config

        if not configs:
            status("No configurations found to choose from")
            return

        # Generate panel cache for quick_panel
        for conf in configs:
            # Read config
            c = dict()
            c['tz_in']  = tz_in if tz_in else conf.get('tz_in')
            c['tz_out'] = tz_out if tz_out else conf.get('tz_out')
            c['format'] = conf.get('format')

            if isinstance(c['format'], basestring):
                c['format'] = c['format'].replace("$default", fdate.default['format'])

            # Do the actual parse action
            try:
                text = fdate.parse(**c)
            except UnknownTimeZoneError as e:
                status(str(e).strip('"'), e)
                return
            except Exception as e:
                status('Error parsing format string `%s`' % format, e)
                return

            self.panel_cache.append([conf['name'], text])
            self.config_map[conf['name']] = c

        self.view.window().show_quick_panel(self.panel_cache, self.on_done)

    def on_done(self, index):
        if index == -1:
            return

        name = self.panel_cache[index][0]
        self.view.run_command("insert_date", self.config_map[name])

# Handle context
def plugin_loaded():
    global s, fdate

    s = Settings(
        sublime.load_settings('insert_date.sublime-settings'),
        settings=dict(
            format=('format', '%c'),
            tz_in=('tz_in', 'local'),
            prompt_config=('prompt_config', []),
            user_prompt_config=('user_prompt_config', [])
        )
    )

    # Register on settings changes
    def on_settings_changed():
        print("InsertDate settings changed")
        # These defaults will be used when the command's parameters are None
        fdate.set_default(s.get_state())

    on_settings_changed()  # Apply initial settings
    s.set_callback(on_settings_changed)

def plugin_unloaded():
    global s

    if s:
        s.clear_callback(True)

# ST2 backwards (and don't call it twice in ST3)
unload_handler = plugin_unloaded if ST2 else lambda: None

# Call manually if on ST2
if ST2:
    plugin_loaded()
<|endoftext|>


<commit_before>#!/usr/bin/env python
"""
instaRaider.py

usage: instaRaider.py [-h] -u USER [-c COUNT]

@amirkurtovic

"""
from bs4 import BeautifulSoup
import selenium.webdriver as webdriver
import re
from time import sleep
import urllib
import urllib2
import os
import sys
import argparse

class instaRaider(object):

    def getImageCount(self, url):
        '''
        Given a url to Instagram profile, return number of photos posted
        '''
        response = urllib2.urlopen(url)
        countsCode = re.search(r'counts\":{\"media\":\d+', response.read())
        count = re.findall(r'\d+', countsCode.group())
        return count[0]

    def loadInstagram(self, profileUrl):
        '''
        Using Selenium WebDriver, load Instagram page to get page source

        '''
        count = self.getImageCount(self.profileUrl)
        print self.userName + " has " + str(count) + " photos on Instagram."

        print "Loading Selenium WebDriver..."

        # Load webdriver and scale window down
        driver = webdriver.Firefox()
        driver.set_window_size(40,40)
        driver.set_window_position(20,20)

        # load Instagram profile and wait for PAUSE
        driver.get(self.profileUrl)
        driver.implicitly_wait(self.PAUSE)

        print "Loading Instagram profile..."

        clicks = (int(count)-60)/20+1

        for x in range(3):
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            sys.stdout.write('.')
            sys.stdout.flush()
            sleep(self.PAUSE)

        # Load full Instagram profile if more than initial 60 photos desired
        if (args.count < 61):
            pass
        else:
            # Click on "Load more..." label
            element = driver.find_element_by_xpath(self.loadLabelXPATH)

            for y in range(clicks):
                element.click()
                driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
                sys.stdout.write('.')
                sys.stdout.flush()
                sleep(self.PAUSE)

        # After load all profile photos, retur source to getPhotos()
        source = BeautifulSoup(driver.page_source)

        # close Firefox window
        driver.close()

        return source

    def validUser(self, userName):
        '''
        returns True if Instagram username is valid
        '''
        # check if Instagram username is valid
        req = urllib2.Request(self.profileUrl)

        try:
            urllib2.urlopen(req)
        except:
            return False
        # if req doesn't fail, user profile exists
        return True

    def photoExists(self, url):
        '''
        Returns true if photo exists
        Used when checking which suffix Instagram used for full-res photo
        url: URL to Instagram photo
        '''
        try:
            urllib2.urlopen(url)
        except:
            return False

        return True

    def getPhotos(self, source, userName, count):
        '''
        Given source code for loaded Instagram page,
        extract all hrefs and download full-resolution photos

        source: HTML source code of Instagram profile papge
        '''
        # directory where photos will be saved
        directory = './Images/' + userName + '/'

        # check if directory exists, if not, make it
        if not os.path.exists(directory):
            os.makedirs(directory)

        # logfile to store urls is csv format
        logfile = './Images/' + userName + '/' + userName + '.csv'
        try:
            file = open(logfile, "a")
        except IOError:
            print "\nLog file does not exist."

        # photo number for file names
        photoNumber = 0

        # indexes for progress bar
        photosSaved = 0
        progressBar = 0

        print "\nRaiding Instagram..."
        print "Saving photos to " + directory

        print "------"
        # print progress bar
        print "Photos saved so far:"
        print "---------10--------20--------30--------40--------50"

        for x in source.findAll('div', {'class':'Image'}):

            if (photoNumber >= count):
                break
            else:
                # increment photonumber for next image
                photoNumber += 1

                #extract url to thumbnail from each photo
                x = x.div
                rawUrl = x['style']

                photoUrl = rawUrl[21:-2]
                photoName = directory + userName + "_" + str(photoNumber) + '.jpg'

                # save full-resolution photo
                urllib.urlretrieve(photoUrl, photoName)

                # save filename and url to CSV file
                file.write(photoUrl + "," + photoName + "\n")

                # print hash to progress bar
                if (photosSaved == 50):
                    photosSaved = 1
                    progressBar += 50
                    sys.stdout.write('\n')
                    sys.stdout.write('#')
                    sys.stdout.flush()

                else:
                    # increment progress bar
                    photosSaved += 1
                    sys.stdout.write('#')
                    sys.stdout.flush()

                sleep(self.PAUSE)

        print "\n------"
        print "Saved " + str(photoNumber) + " images to " + directory

        # close logfile
        file.close()
        print "Saved activity in logfile: " + logfile

    def __init__(self, userName):
        self.userName = userName
        self.profileUrl = 'http://instagram.com/' + userName + '/'
        self.fullResSuffixes = ['7.jpg', '8.jpg', 'n.jpg']
        self.PAUSE = 1
        self.loadLabelXPATH = "/html/body/div/div/div/section/div/span/a/span[2]/span/span"

if __name__ == '__main__':

    # parse arguments
    parser = argparse.ArgumentParser(description="InstaRaider")
    parser.add_argument('-u', '--user', help="Instagram username", required=True)
    parser.add_argument('-c', '--count', help="# of photos to download", type=int)
    args = parser.parse_args()

    if (args.user):
        userName = args.user

        raider = instaRaider(userName)
        url = raider.profileUrl

        if(raider.validUser(userName)):

            if not args.count:
                count = raider.getImageCount(url)
            else:
                count = args.count
                if raider.getImageCount(url) < count:
                    print "You want to dowload %r photos." % args.count
                    print "The user only has %r photo." % raider.getImageCount(url)
                    print "Downloading all photos."
                    count = raider.getImageCount(url)

            # Get source code from fully loaded Instagram profile page
            source = raider.loadInstagram(url)

            # Download all photos identified on profile page
            raider.getPhotos(source, userName, count)
        else:
            print "Username " + userName + " is not valid."
        <commit_msg>Minor change<commit_after>#!/usr/bin/env python
"""
instaRaider.py

usage: instaRaider.py [-h] -u USER [-c COUNT]

@amirkurtovic

"""
from bs4 import BeautifulSoup
import selenium.webdriver as webdriver
import re
from time import sleep
import urllib
import urllib2
import os
import sys
import argparse

class instaRaider(object):

    def getImageCount(self, url):
        '''
        Given a url to Instagram profile, return number of photos posted
        '''
        response = urllib2.urlopen(url)
        countsCode = re.search(r'counts\":{\"media\":\d+', response.read())
        count = re.findall(r'\d+', countsCode.group())
        return count[0]

    def loadInstagram(self, profileUrl):
        '''
        Using Selenium WebDriver, load Instagram page to get page source

        '''
        count = self.getImageCount(self.profileUrl)
        print self.userName + " has " + str(count) + " photos on Instagram."

        print "Loading Selenium WebDriver..."

        # Load webdriver and scale window down
        driver = webdriver.Firefox()
        driver.set_window_size(40,40)
        driver.set_window_position(20,20)

        # load Instagram profile and wait for PAUSE
        driver.get(self.profileUrl)
        driver.implicitly_wait(self.PAUSE)

        print "Loading Instagram profile..."

        clicks = (int(count)-60)/20+1

        for x in range(3):
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            sys.stdout.write('.')
            sys.stdout.flush()
            sleep(self.PAUSE)

        # Load full Instagram profile if more than initial 60 photos desired
        if (args.count < 61):
            pass
        else:
            # Click on "Load more..." label
            element = driver.find_element_by_xpath(self.loadLabelXPATH)

            for y in range(clicks):
                element.click()
                driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
                sys.stdout.write('.')
                sys.stdout.flush()
                sleep(self.PAUSE)

        # After load all profile photos, retur source to getPhotos()
        source = BeautifulSoup(driver.page_source)

        # close Firefox window
        driver.close()

        return source

    def validUser(self, userName):
        '''
        returns True if Instagram username is valid
        '''
        # check if Instagram username is valid
        req = urllib2.Request(self.profileUrl)

        try:
            urllib2.urlopen(req)
        except:
            return False
        # if req doesn't fail, user profile exists
        return True

    def photoExists(self, url):
        '''
        Returns true if photo exists
        Used when checking which suffix Instagram used for full-res photo
        url: URL to Instagram photo
        '''
        try:
            urllib2.urlopen(url)
        except:
            return False

        return True

    def getPhotos(self, source, userName, count):
        '''
        Given source code for loaded Instagram page,
        extract all hrefs and download full-resolution photos

        source: HTML source code of Instagram profile papge
        '''
        # directory where photos will be saved
        directory = './Images/' + userName + '/'

        # check if directory exists, if not, make it
        if not os.path.exists(directory):
            os.makedirs(directory)

        # logfile to store urls is csv format
        logfile = './Images/' + userName + '/' + userName + '.csv'
        try:
            file = open(logfile, "a")
        except IOError:
            print "\nLog file does not exist."

        # photo number for file names
        photoNumber = 0

        # indexes for progress bar
        photosSaved = 0
        progressBar = 0

        print "\nRaiding Instagram..."
        print "Saving photos to " + directory

        print "------"
        # print progress bar
        print "Photos saved so far:"
        print "---------10--------20--------30--------40--------50"

        for x in source.findAll('div', {'class':'Image'}):

            if (photoNumber >= count):
                break
            else:
                # increment photonumber for next image
                photoNumber += 1

                #extract url to thumbnail from each photo
                x = x.div
                rawUrl = x['style']

                photoUrl = rawUrl[21:-2]
                photoName = directory + userName + "_" + str(photoNumber) + '.jpg'

                # save full-resolution photo
                urllib.urlretrieve(photoUrl, photoName)

                # save filename and url to CSV file
                file.write(photoUrl + "," + photoName + "\n")

                # print hash to progress bar
                if (photosSaved == 50):
                    photosSaved = 1
                    progressBar += 50
                    sys.stdout.write('\n')
                    sys.stdout.write('#')
                    sys.stdout.flush()

                else:
                    # increment progress bar
                    photosSaved += 1
                    sys.stdout.write('#')
                    sys.stdout.flush()

                sleep(self.PAUSE)

        print "\n------"
        print "Saved " + str(photoNumber) + " images to " + directory

        # close logfile
        file.close()
        print "Saved activity in logfile: " + logfile

    def __init__(self, userName):
        self.userName = userName
        self.profileUrl = 'http://instagram.com/' + userName + '/'
        self.PAUSE = 1
        self.loadLabelXPATH = "/html/body/div/div/div/section/div/span/a/span[2]/span/span"

if __name__ == '__main__':

    # parse arguments
    parser = argparse.ArgumentParser(description="InstaRaider")
    parser.add_argument('-u', '--user', help="Instagram username", required=True)
    parser.add_argument('-c', '--count', help="# of photos to download", type=int)
    args = parser.parse_args()

    if (args.user):
        userName = args.user

        raider = instaRaider(userName)
        url = raider.profileUrl

        if(raider.validUser(userName)):

            if not args.count:
                count = raider.getImageCount(url)
            else:
                count = args.count
                if raider.getImageCount(url) < count:
                    print "You want to dowload %r photos." % args.count
                    print "The user only has %r photo." % raider.getImageCount(url)
                    print "Downloading all photos."
                    count = raider.getImageCount(url)

            # Get source code from fully loaded Instagram profile page
            source = raider.loadInstagram(url)

            # Download all photos identified on profile page
            raider.getPhotos(source, userName, count)
        else:
            print "Username " + userName + " is not valid."
        <|endoftext|>


<commit_before>#!/usr/bin/env python
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Adaptor script called through build/isolate.gypi.

Creates a wrapping .isolate which 'includes' the original one, that can be
consumed by tools/swarming_client/isolate.py. Path variables are determined
based on the current working directory. The relative_cwd in the .isolated file
is determined based on the .isolate file that declare the 'command' variable to
be used so the wrapping .isolate doesn't affect this value.

This script loads build.ninja and processes it to determine all the executables
referenced by the isolated target. It adds them in the wrapping .isolate file.

WARNING: The target to use for build.ninja analysis is the base name of the
.isolate file plus '_run'. For example, 'foo_test.isolate' would have the target
'foo_test_run' analysed.
"""

import glob
import json
import logging
import os
import posixpath
import StringIO
import subprocess
import sys
import time

TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
SWARMING_CLIENT_DIR = os.path.join(TOOLS_DIR, 'swarming_client')
SRC_DIR = os.path.dirname(TOOLS_DIR)

sys.path.insert(0, SWARMING_CLIENT_DIR)

import isolate_format

def load_ninja_recursively(build_dir, ninja_path, build_steps):
  """Crudely extracts all the subninja and build referenced in ninja_path.

  In particular, it ignores rule and variable declarations. The goal is to be
  performant (well, as much as python can be performant) which is currently in
  the <200ms range for a complete chromium tree. As such the code is laid out
  for performance instead of readability.
  """
  logging.debug('Loading %s', ninja_path)
  try:
    with open(os.path.join(build_dir, ninja_path), 'rb') as f:
      line = None
      merge_line = ''
      subninja = []
      for line in f:
        line = line.rstrip()
        if not line:
          continue

        if line[-1] == '$':
          # The next line needs to be merged in.
          merge_line += line[:-1]
          continue

        if merge_line:
          line = merge_line + line
          merge_line = ''

        statement = line[:line.find(' ')]
        if statement == 'build':
          # Save the dependency list as a raw string. Only the lines needed will
          # be processed with raw_build_to_deps(). This saves a good 70ms of
          # processing time.
          build_target, dependencies = line[6:].split(': ', 1)
          # Interestingly, trying to be smart and only saving the build steps
          # with the intended extensions ('', '.stamp', '.so') slows down
          # parsing even if 90% of the build rules can be skipped.
          # On Windows, a single step may generate two target, so split items
          # accordingly. It has only been seen for .exe/.exe.pdb combos.
          for i in build_target.strip().split():
            build_steps[i] = dependencies
        elif statement == 'subninja':
          subninja.append(line[9:])
  except IOError:
    print >> sys.stderr, 'Failed to open %s' % ninja_path
    raise

  total = 1
  for rel_path in subninja:
    try:
      # Load each of the files referenced.
      # TODO(maruel): Skip the files known to not be needed. It saves an aweful
      # lot of processing time.
      total += load_ninja_recursively(build_dir, rel_path, build_steps)
    except IOError:
      print >> sys.stderr, '... as referenced by %s' % ninja_path
      raise
  return total

def load_ninja(build_dir):
  """Loads the tree of .ninja files in build_dir."""
  build_steps = {}
  total = load_ninja_recursively(build_dir, 'build.ninja', build_steps)
  logging.info('Loaded %d ninja files, %d build steps', total, len(build_steps))
  return build_steps

def using_blacklist(item):
  """Returns True if an item should be analyzed.

  Ignores many rules that are assumed to not depend on a dynamic library. If
  the assumption doesn't hold true anymore for a file format, remove it from
  this list. This is simply an optimization.
  """
  IGNORED = (
    '.a', '.cc', '.css', '.def', '.frag', '.h', '.html', '.js', '.json',
    '.manifest', '.o', '.obj', '.pak', '.png', '.pdb', '.strings', '.test',
    '.txt', '.vert',
  )
  # ninja files use native path format.
  ext = os.path.splitext(item)[1]
  if ext in IGNORED:
    return False
  # Special case Windows, keep .dll.lib but discard .lib.
  if item.endswith('.dll.lib'):
    return True
  if ext == '.lib':
    return False
  return item not in ('', '|', '||')

def raw_build_to_deps(item):
  """Converts a raw ninja build statement into the list of interesting
  dependencies.
  """
  # TODO(maruel): Use a whitelist instead? .stamp, .so.TOC, .dylib.TOC,
  # .dll.lib, .exe and empty.
  # The first item is the build rule, e.g. 'link', 'cxx', 'phony', etc.
  return filter(using_blacklist, item.split(' ')[1:])

def collect_deps(target, build_steps, dependencies_added, rules_seen):
  """Recursively adds all the interesting dependencies for |target|
  into |dependencies_added|.
  """
  if rules_seen is None:
    rules_seen = set()
  if target in rules_seen:
    # TODO(maruel): Figure out how it happens.
    logging.warning('Circular dependency for %s!', target)
    return
  rules_seen.add(target)
  try:
    dependencies = raw_build_to_deps(build_steps[target])
  except KeyError:
    logging.info('Failed to find a build step to generate: %s', target)
    return
  logging.debug('collect_deps(%s) -> %s', target, dependencies)
  for dependency in dependencies:
    dependencies_added.add(dependency)
    collect_deps(dependency, build_steps, dependencies_added, rules_seen)

def post_process_deps(build_dir, dependencies):
  """Processes the dependency list with OS specific rules."""
  def filter_item(i):
    if i.endswith('.so.TOC'):
      # Remove only the suffix .TOC, not the .so!
      return i[:-4]
    if i.endswith('.dylib.TOC'):
      # Remove only the suffix .TOC, not the .dylib!
      return i[:-4]
    if i.endswith('.dll.lib'):
      # Remove only the suffix .lib, not the .dll!
      return i[:-4]
    return i

  def f(i):
    # Check for execute access and strip directories. This gets rid of all the
    # phony rules.
    p = os.path.join(build_dir, i)
    return os.access(p, os.X_OK) and not os.path.isdir(p)

  return filter(f, map(filter_item, dependencies))

def create_wrapper(args, isolate_index, isolated_index):
  """Creates a wrapper .isolate that add dynamic libs.

  The original .isolate is not modified.
  """
  cwd = os.getcwd()
  isolate = args[isolate_index]
  # The code assumes the .isolate file is always specified path-less in cwd. Fix
  # if this assumption doesn't hold true.
  assert os.path.basename(isolate) == isolate, isolate

  # This will look like ../out/Debug. This is based against cwd. Note that this
  # must equal the value provided as PRODUCT_DIR.
  build_dir = os.path.dirname(args[isolated_index])

  # This will look like chrome/unit_tests.isolate. It is based against SRC_DIR.
  # It's used to calculate temp_isolate.
  src_isolate = os.path.relpath(os.path.join(cwd, isolate), SRC_DIR)

  # The wrapping .isolate. This will look like
  # ../out/Debug/gen/chrome/unit_tests.isolate.
  temp_isolate = os.path.join(build_dir, 'gen', src_isolate)
  temp_isolate_dir = os.path.dirname(temp_isolate)

  # Relative path between the new and old .isolate file.
  isolate_relpath = os.path.relpath(
      '.', temp_isolate_dir).replace(os.path.sep, '/')

  # It's a big assumption here that the name of the isolate file matches the
  # primary target '_run'. Fix accordingly if this doesn't hold true, e.g.
  # complain to maruel@.
  target = isolate[:-len('.isolate')] + '_run'
  build_steps = load_ninja(build_dir)
  binary_deps = set()
  collect_deps(target, build_steps, binary_deps, None)
  binary_deps = post_process_deps(build_dir, binary_deps)
  logging.debug(
      'Binary dependencies:%s', ''.join('\n  ' + i for i in binary_deps))

  # Now do actual wrapping .isolate.
  isolate_dict = {
    'includes': [
      posixpath.join(isolate_relpath, isolate),
    ],
    'variables': {
      # Will look like ['<(PRODUCT_DIR)/lib/flibuser_prefs.so'].
      isolate_format.KEY_TRACKED: sorted(
          '<(PRODUCT_DIR)/%s' % i.replace(os.path.sep, '/')
          for i in binary_deps),
    },
  }
  if not os.path.isdir(temp_isolate_dir):
    os.makedirs(temp_isolate_dir)
  comment = (
      '# Warning: this file was AUTOGENERATED.\n'
      '# DO NO EDIT.\n')
  out = StringIO.StringIO()
  isolate_format.print_all(comment, isolate_dict, out)
  isolate_content = out.getvalue()
  with open(temp_isolate, 'wb') as f:
    f.write(isolate_content)
  logging.info('Added %d dynamic libs', len(binary_deps))
  logging.debug('%s', isolate_content)
  args[isolate_index] = temp_isolate

def prepare_isolate_call(args, output):
  """Gathers all information required to run isolate.py later.

  Dumps it as JSON to |output| file.
  """
  with open(output, 'wb') as f:
    json.dump({
      'args': args,
      'dir': os.getcwd(),
      'version': 1,
    }, f, indent=2, sort_keys=True)

def main():
  logging.basicConfig(level=logging.ERROR, format='%(levelname)7s %(message)s')
  args = sys.argv[1:]
  mode = args[0] if args else None
  isolate = None
  isolated = None
  is_component = False
  for i, arg in enumerate(args):
    if arg == '--isolate':
      isolate = i + 1
    if arg == '--isolated':
      isolated = i + 1
    if arg == 'component=shared_library':
      is_component = True
  if isolate is None or isolated is None or not mode:
    print >> sys.stderr, 'Internal failure'
    return 1

  if is_component:
    create_wrapper(args, isolate, isolated)

  # In 'prepare' mode just collect all required information for postponed
  # isolated.py invocation later, store it in *.isolated.gen.json file.
  if mode == 'prepare':
    prepare_isolate_call(args[1:], args[isolated] + '.gen.json')
    return 0

  swarming_client = os.path.join(SRC_DIR, 'tools', 'swarming_client')
  sys.stdout.flush()
  result = subprocess.call(
      [sys.executable, os.path.join(swarming_client, 'isolate.py')] + args)
  return result

if __name__ == '__main__':
  sys.exit(main())
<commit_msg>Make isolate_driver.py only add .dll/.so/.dylib for component builds' generated files.<commit_after>#!/usr/bin/env python
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Adaptor script called through build/isolate.gypi.

Creates a wrapping .isolate which 'includes' the original one, that can be
consumed by tools/swarming_client/isolate.py. Path variables are determined
based on the current working directory. The relative_cwd in the .isolated file
is determined based on the .isolate file that declare the 'command' variable to
be used so the wrapping .isolate doesn't affect this value.

This script loads build.ninja and processes it to determine all the executables
referenced by the isolated target. It adds them in the wrapping .isolate file.

WARNING: The target to use for build.ninja analysis is the base name of the
.isolate file plus '_run'. For example, 'foo_test.isolate' would have the target
'foo_test_run' analysed.
"""

import glob
import json
import logging
import os
import posixpath
import StringIO
import subprocess
import sys
import time

TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
SWARMING_CLIENT_DIR = os.path.join(TOOLS_DIR, 'swarming_client')
SRC_DIR = os.path.dirname(TOOLS_DIR)

sys.path.insert(0, SWARMING_CLIENT_DIR)

import isolate_format

def load_ninja_recursively(build_dir, ninja_path, build_steps):
  """Crudely extracts all the subninja and build referenced in ninja_path.

  In particular, it ignores rule and variable declarations. The goal is to be
  performant (well, as much as python can be performant) which is currently in
  the <200ms range for a complete chromium tree. As such the code is laid out
  for performance instead of readability.
  """
  logging.debug('Loading %s', ninja_path)
  try:
    with open(os.path.join(build_dir, ninja_path), 'rb') as f:
      line = None
      merge_line = ''
      subninja = []
      for line in f:
        line = line.rstrip()
        if not line:
          continue

        if line[-1] == '$':
          # The next line needs to be merged in.
          merge_line += line[:-1]
          continue

        if merge_line:
          line = merge_line + line
          merge_line = ''

        statement = line[:line.find(' ')]
        if statement == 'build':
          # Save the dependency list as a raw string. Only the lines needed will
          # be processed with raw_build_to_deps(). This saves a good 70ms of
          # processing time.
          build_target, dependencies = line[6:].split(': ', 1)
          # Interestingly, trying to be smart and only saving the build steps
          # with the intended extensions ('', '.stamp', '.so') slows down
          # parsing even if 90% of the build rules can be skipped.
          # On Windows, a single step may generate two target, so split items
          # accordingly. It has only been seen for .exe/.exe.pdb combos.
          for i in build_target.strip().split():
            build_steps[i] = dependencies
        elif statement == 'subninja':
          subninja.append(line[9:])
  except IOError:
    print >> sys.stderr, 'Failed to open %s' % ninja_path
    raise

  total = 1
  for rel_path in subninja:
    try:
      # Load each of the files referenced.
      # TODO(maruel): Skip the files known to not be needed. It saves an aweful
      # lot of processing time.
      total += load_ninja_recursively(build_dir, rel_path, build_steps)
    except IOError:
      print >> sys.stderr, '... as referenced by %s' % ninja_path
      raise
  return total

def load_ninja(build_dir):
  """Loads the tree of .ninja files in build_dir."""
  build_steps = {}
  total = load_ninja_recursively(build_dir, 'build.ninja', build_steps)
  logging.info('Loaded %d ninja files, %d build steps', total, len(build_steps))
  return build_steps

def using_blacklist(item):
  """Returns True if an item should be analyzed.

  Ignores many rules that are assumed to not depend on a dynamic library. If
  the assumption doesn't hold true anymore for a file format, remove it from
  this list. This is simply an optimization.
  """
  IGNORED = (
    '.a', '.cc', '.css', '.def', '.frag', '.h', '.html', '.js', '.json',
    '.manifest', '.o', '.obj', '.pak', '.png', '.pdb', '.strings', '.test',
    '.txt', '.vert',
  )
  # ninja files use native path format.
  ext = os.path.splitext(item)[1]
  if ext in IGNORED:
    return False
  # Special case Windows, keep .dll.lib but discard .lib.
  if item.endswith('.dll.lib'):
    return True
  if ext == '.lib':
    return False
  return item not in ('', '|', '||')

def raw_build_to_deps(item):
  """Converts a raw ninja build statement into the list of interesting
  dependencies.
  """
  # TODO(maruel): Use a whitelist instead? .stamp, .so.TOC, .dylib.TOC,
  # .dll.lib, .exe and empty.
  # The first item is the build rule, e.g. 'link', 'cxx', 'phony', etc.
  return filter(using_blacklist, item.split(' ')[1:])

def collect_deps(target, build_steps, dependencies_added, rules_seen):
  """Recursively adds all the interesting dependencies for |target|
  into |dependencies_added|.
  """
  if rules_seen is None:
    rules_seen = set()
  if target in rules_seen:
    # TODO(maruel): Figure out how it happens.
    logging.warning('Circular dependency for %s!', target)
    return
  rules_seen.add(target)
  try:
    dependencies = raw_build_to_deps(build_steps[target])
  except KeyError:
    logging.info('Failed to find a build step to generate: %s', target)
    return
  logging.debug('collect_deps(%s) -> %s', target, dependencies)
  for dependency in dependencies:
    dependencies_added.add(dependency)
    collect_deps(dependency, build_steps, dependencies_added, rules_seen)

def post_process_deps(build_dir, dependencies):
  """Processes the dependency list with OS specific rules."""
  def filter_item(i):
    if i.endswith('.so.TOC'):
      # Remove only the suffix .TOC, not the .so!
      return i[:-4]
    if i.endswith('.dylib.TOC'):
      # Remove only the suffix .TOC, not the .dylib!
      return i[:-4]
    if i.endswith('.dll.lib'):
      # Remove only the suffix .lib, not the .dll!
      return i[:-4]
    return i

  def f(i):
    # This script is only for adding new binaries that are created as part of
    # the component build.
    ext = os.path.splitext(i)[1]
    if ext not in ['.dll', '.so', '.dylib']:
      return False

    # Check for execute access and strip directories. This gets rid of all the
    # phony rules.
    p = os.path.join(build_dir, i)
    return os.access(p, os.X_OK) and not os.path.isdir(p)

  return filter(f, map(filter_item, dependencies))

def create_wrapper(args, isolate_index, isolated_index):
  """Creates a wrapper .isolate that add dynamic libs.

  The original .isolate is not modified.
  """
  cwd = os.getcwd()
  isolate = args[isolate_index]
  # The code assumes the .isolate file is always specified path-less in cwd. Fix
  # if this assumption doesn't hold true.
  assert os.path.basename(isolate) == isolate, isolate

  # This will look like ../out/Debug. This is based against cwd. Note that this
  # must equal the value provided as PRODUCT_DIR.
  build_dir = os.path.dirname(args[isolated_index])

  # This will look like chrome/unit_tests.isolate. It is based against SRC_DIR.
  # It's used to calculate temp_isolate.
  src_isolate = os.path.relpath(os.path.join(cwd, isolate), SRC_DIR)

  # The wrapping .isolate. This will look like
  # ../out/Debug/gen/chrome/unit_tests.isolate.
  temp_isolate = os.path.join(build_dir, 'gen', src_isolate)
  temp_isolate_dir = os.path.dirname(temp_isolate)

  # Relative path between the new and old .isolate file.
  isolate_relpath = os.path.relpath(
      '.', temp_isolate_dir).replace(os.path.sep, '/')

  # It's a big assumption here that the name of the isolate file matches the
  # primary target '_run'. Fix accordingly if this doesn't hold true, e.g.
  # complain to maruel@.
  target = isolate[:-len('.isolate')] + '_run'
  build_steps = load_ninja(build_dir)
  binary_deps = set()
  collect_deps(target, build_steps, binary_deps, None)
  binary_deps = post_process_deps(build_dir, binary_deps)
  logging.debug(
      'Binary dependencies:%s', ''.join('\n  ' + i for i in binary_deps))

  # Now do actual wrapping .isolate.
  isolate_dict = {
    'includes': [
      posixpath.join(isolate_relpath, isolate),
    ],
    'variables': {
      # Will look like ['<(PRODUCT_DIR)/lib/flibuser_prefs.so'].
      isolate_format.KEY_TRACKED: sorted(
          '<(PRODUCT_DIR)/%s' % i.replace(os.path.sep, '/')
          for i in binary_deps),
    },
  }
  if not os.path.isdir(temp_isolate_dir):
    os.makedirs(temp_isolate_dir)
  comment = (
      '# Warning: this file was AUTOGENERATED.\n'
      '# DO NO EDIT.\n')
  out = StringIO.StringIO()
  isolate_format.print_all(comment, isolate_dict, out)
  isolate_content = out.getvalue()
  with open(temp_isolate, 'wb') as f:
    f.write(isolate_content)
  logging.info('Added %d dynamic libs', len(binary_deps))
  logging.debug('%s', isolate_content)
  args[isolate_index] = temp_isolate

def prepare_isolate_call(args, output):
  """Gathers all information required to run isolate.py later.

  Dumps it as JSON to |output| file.
  """
  with open(output, 'wb') as f:
    json.dump({
      'args': args,
      'dir': os.getcwd(),
      'version': 1,
    }, f, indent=2, sort_keys=True)

def main():
  logging.basicConfig(level=logging.ERROR, format='%(levelname)7s %(message)s')
  args = sys.argv[1:]
  mode = args[0] if args else None
  isolate = None
  isolated = None
  is_component = False
  for i, arg in enumerate(args):
    if arg == '--isolate':
      isolate = i + 1
    if arg == '--isolated':
      isolated = i + 1
    if arg == 'component=shared_library':
      is_component = True
  if isolate is None or isolated is None or not mode:
    print >> sys.stderr, 'Internal failure'
    return 1

  if is_component:
    create_wrapper(args, isolate, isolated)

  # In 'prepare' mode just collect all required information for postponed
  # isolated.py invocation later, store it in *.isolated.gen.json file.
  if mode == 'prepare':
    prepare_isolate_call(args[1:], args[isolated] + '.gen.json')
    return 0

  swarming_client = os.path.join(SRC_DIR, 'tools', 'swarming_client')
  sys.stdout.flush()
  result = subprocess.call(
      [sys.executable, os.path.join(swarming_client, 'isolate.py')] + args)
  return result

if __name__ == '__main__':
  sys.exit(main())
<|endoftext|>


<commit_before># -*- coding: UTF-8 -*-
# Copyright 2016 Luc Saffre
# This file is part of Lino Welfare.
#
# Lino Welfare is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# Lino Welfare is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License along with Lino Welfare.  If not, see
# <http://www.gnu.org/licenses/>.

"""Choicelists for `lino_welfare.modlib.esf`.

"""
from __future__ import unicode_literals
import datetime

from django.db import models

from lino.api import dd, _
from lino.utils.dates import weekdays
from lino.utils.quantities import Duration

ZERO = Duration("0:00")

class ParticipationCertificates(dd.ChoiceList):
    verbose_name = _("Participation Certificate")
    verbose_name_plural = _("Participation Certificates")
add = ParticipationCertificates.add_item
add('10', _("Epreuve d’évaluation réussie sans titre spécifique"))

class StatisticalField(dd.Choice):

    field_name = None
    field = None

    def __init__(self, value, text, name=None, **kwargs):
        super(StatisticalField, self).__init__(
            value, text, name, **kwargs)
        self.field_name = "esf" + value
        self.field = self.create_field()

    def collect_from_guest(self, obj, summary):
        pass

    def collect_from_immersion_contract(self, obj, summary):
        pass

    def collect_from_jobs_contract(self, obj, summary):
        pass

class GuestCount(StatisticalField):
    """Not used in reality."""
    def create_field(self):
        return models.IntegerField(
            self.value, default=0, help_text=self.text)

    def collect_from_guest(self, obj, summary):
        if obj.event.event_type is None:
            return 0
        sf = obj.event.event_type.esf_field
        if sf is not None and sf.value == self.value:
            return 1
        return 0

from atelier.utils import last_day_of_month
class HoursField(StatisticalField):
    def create_field(self):
        return dd.DurationField(
            self.value, default=ZERO,
            help_text=self.text)

    def daterange2hours(self, sd, ed, summary):
        if sd and ed:
            ssd = datetime.date(summary.year, summary.month or 1, 1)
            sd = max(sd, ssd)

            sed = last_day_of_month(
                datetime.date(summary.year, summary.month or 12, 1))
            ed = min(ed, sed)
            nb_of_days = weekdays(sd, ed)
            return Duration("38:00") * nb_of_days / 7
            # return Duration("8:00") * nb_of_days

class GuestHours(HoursField):
    """Count the real hours of presence."""
    def collect_from_guest(self, obj, summary):
        # obj is a `cal.Guest` instance
        if obj.event.event_type is None:
            return
        sf = obj.event.event_type.esf_field
        if sf is None or sf.value != self.value:
            return
        if obj.gone_since is None or obj.busy_since is None:
            return
        return Duration(obj.gone_since - obj.busy_since)

class GuestHoursFixed(HoursField):
    """Count a fixed time for each presence."""

    hours_per_guest = Duration('1:00')

    def collect_from_guest(self, obj, summary):
        if obj.event.event_type is None:
            return
        sf = obj.event.event_type.esf_field
        if sf is None or sf.value != self.value:
            return
        return sf.hours_per_guest

class ImmersionHours(HoursField):

    def collect_from_immersion_contract(self, obj, summary):
        # obj is a `immersion.Contract` instance
        return self.daterange2hours(
            obj.applies_from, obj.date_ended, summary)

class Art60Hours(HoursField):
    def collect_from_jobs_contract(self, obj, summary):
        # obj is a `jobs.Contract` instance
        return self.daterange2hours(
            obj.applies_from, obj.date_ended, summary)
        # return Duration("8:00") * obj.duration

class StatisticalFields(dd.ChoiceList):
    verbose_name = _("ESF field")
    verbose_name_plural = _("ESF fields")
    column_names = 'value name text type'
    item_class = StatisticalField

add = StatisticalFields.add_item_instance

# Séance d'info
add(GuestHoursFixed('10', _("Informative sessions"),
                    hours_per_guest=Duration('2:00')))

# Entretien individuel
add(GuestHoursFixed('20', _("Individual consultation")))

# Evaluation formation externe et art.61
add(GuestHoursFixed('21', _("Evaluation of external training")))

# S.I.S. agréé
add(GuestHoursFixed('30', _("Certified integration service")))

# Tests de niveau
add(GuestHoursFixed('40', _("Level tests")))

# Initiation informatique
add(GuestHoursFixed('41', _("IT basics")))

# Mobilité
add(GuestHoursFixed('42', _("Mobility")))

# Remédiation mathématique et français
add(GuestHoursFixed('43', _("Remedial teaching")))

# Activons-nous
add(GuestHours('44', _("Wake up!")))

# Mise en situation professionnelle : calculer les heures par stage
# d'immersion, en fonction des dates de début et de fin et de
# l'horaire de travail.
add(ImmersionHours('50', _("Getting a professional situation")))

# Cyber-employ : Somme des présences aux ateliers "Cyber-emploi", mais
# pour ces ateliers on note les heures d'arrivée et de départ par
# participation.
add(GuestHours('60', _("Cyber Job")))

# Mise à l’emploi sous contrat art.60§7
add(Art60Hours('70', _("Art 60§7 job supplyment")))

@dd.receiver(dd.pre_analyze)
def inject_statistical_fields(sender, **kw):
    for sf in StatisticalFields.items():
        if sf.field_name is not None:
            dd.inject_field('esf.ClientSummary', sf.field_name, sf.field)

dd.inject_field(
    'cal.EventType', 'esf_field', StatisticalFields.field(blank=True))
<commit_msg>http://luc.lino-framework.org/blog/2016/1121.html<commit_after># -*- coding: UTF-8 -*-
# Copyright 2016 Luc Saffre
# This file is part of Lino Welfare.
#
# Lino Welfare is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# Lino Welfare is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License along with Lino Welfare.  If not, see
# <http://www.gnu.org/licenses/>.

"""Choicelists for `lino_welfare.modlib.esf`.

"""
from __future__ import unicode_literals
import datetime

from django.db import models

from lino.api import dd, _
from lino.utils.dates import weekdays
from lino.utils.quantities import Duration
from lino_xl.lib.cal.choicelists import GuestStates

ZERO = Duration("0:00")

class ParticipationCertificates(dd.ChoiceList):
    verbose_name = _("Participation Certificate")
    verbose_name_plural = _("Participation Certificates")
add = ParticipationCertificates.add_item
add('10', _("Epreuve d’évaluation réussie sans titre spécifique"))

class StatisticalField(dd.Choice):

    field_name = None
    field = None

    def __init__(self, value, text, name=None, **kwargs):
        super(StatisticalField, self).__init__(
            value, text, name, **kwargs)
        self.field_name = "esf" + value
        self.field = self.create_field()

    def collect_from_guest(self, obj, summary):
        pass

    def collect_from_immersion_contract(self, obj, summary):
        pass

    def collect_from_jobs_contract(self, obj, summary):
        pass

class GuestCount(StatisticalField):
    """Not used in reality."""
    def create_field(self):
        return models.IntegerField(
            self.value, default=0, help_text=self.text)

    def collect_from_guest(self, obj, summary):
        if obj.event.event_type is None:
            return 0
        sf = obj.event.event_type.esf_field
        if sf is not None and sf.value == self.value:
            return 1
        return 0

from atelier.utils import last_day_of_month
class HoursField(StatisticalField):
    def create_field(self):
        return dd.DurationField(
            self.value, default=ZERO,
            help_text=self.text)

    def daterange2hours(self, sd, ed, summary):
        if sd and ed:
            ssd = datetime.date(summary.year, summary.month or 1, 1)
            sd = max(sd, ssd)

            sed = last_day_of_month(
                datetime.date(summary.year, summary.month or 12, 1))
            ed = min(ed, sed)
            nb_of_days = weekdays(sd, ed)
            return Duration("38:00") * nb_of_days / 7
            # return Duration("8:00") * nb_of_days

class GuestHours(HoursField):
    """Count the real hours of presence."""
    def collect_from_guest(self, obj, summary):
        # obj is a `cal.Guest` instance
        if obj.event.event_type is None:
            return
        sf = obj.event.event_type.esf_field
        if sf is None or sf.value != self.value:
            return
        if obj.gone_since is None or obj.busy_since is None:
            return
        return Duration(obj.gone_since - obj.busy_since)

class GuestHoursEvent(HoursField):
    """Count the event's duration for each presence."""

    def collect_from_guest(self, obj, summary):
        if obj.event.event_type is None:
            return
        sf = obj.event.event_type.esf_field
        if sf is None or sf.value != self.value:
            return
        if obj.state != GuestStates.present:
            return
        return obj.event.get_duration()

class GuestHoursFixed(HoursField):
    """Count a fixed time for each presence."""

    hours_per_guest = Duration('1:00')

    def collect_from_guest(self, obj, summary):
        if obj.event.event_type is None:
            return
        sf = obj.event.event_type.esf_field
        if sf is None or sf.value != self.value:
            return
        return sf.hours_per_guest

class ImmersionHours(HoursField):

    def collect_from_immersion_contract(self, obj, summary):
        # obj is a `immersion.Contract` instance
        return self.daterange2hours(
            obj.applies_from, obj.date_ended, summary)

class Art60Hours(HoursField):
    def collect_from_jobs_contract(self, obj, summary):
        # obj is a `jobs.Contract` instance
        return self.daterange2hours(
            obj.applies_from, obj.date_ended, summary)
        # return Duration("8:00") * obj.duration

class StatisticalFields(dd.ChoiceList):
    verbose_name = _("ESF field")
    verbose_name_plural = _("ESF fields")
    column_names = 'value name text type'
    item_class = StatisticalField

add = StatisticalFields.add_item_instance

# Séance d'info
add(GuestHoursFixed('10', _("Informative sessions"),
                    hours_per_guest=Duration('2:00')))

# Entretien individuel
add(GuestHoursFixed('20', _("Individual consultation")))

# Evaluation formation externe et art.61
add(GuestHoursFixed('21', _("Evaluation of external training")))

# S.I.S. agréé
add(GuestHoursFixed('30', _("Certified integration service")))

# Tests de niveau
add(GuestHoursFixed('40', _("Level tests")))

# Initiation informatique
add(GuestHoursFixed('41', _("IT basics")))

# Mobilité
add(GuestHoursFixed('42', _("Mobility")))

# Remédiation mathématique et français
add(GuestHoursFixed('43', _("Remedial teaching")))

# Activons-nous
add(GuestHoursEvent('44', _("Wake up!")))

# Mise en situation professionnelle : calculer les heures par stage
# d'immersion, en fonction des dates de début et de fin et de
# l'horaire de travail.
add(ImmersionHours('50', _("Getting a professional situation")))

# Cyber-employ : Somme des présences aux ateliers "Cyber-emploi", mais
# pour ces ateliers on note les heures d'arrivée et de départ par
# participation.
add(GuestHoursEvent('60', _("Cyber Job")))

# Mise à l’emploi sous contrat art.60§7
add(Art60Hours('70', _("Art 60§7 job supplyment")))

@dd.receiver(dd.pre_analyze)
def inject_statistical_fields(sender, **kw):
    for sf in StatisticalFields.items():
        if sf.field_name is not None:
            dd.inject_field('esf.ClientSummary', sf.field_name, sf.field)

dd.inject_field(
    'cal.EventType', 'esf_field', StatisticalFields.field(blank=True))
<|endoftext|>