<commit_before># -*- coding: utf-8 -*-
'''
    Mango teams system logic.
'''

# This file is part of mango.

# Distributed under the terms of the last AGPL License.
# The full license is in the file LICENCE, distributed as part of this software.

__author__ = 'Team Machine'

import uuid
import logging
import ujson as json
from tornado import gen
from schematics.types import compound
from mango.messages import teams
from mango.messages import BaseResult
from mango.structures.teams import TeamMap
from riak.datatypes import Map
from mango.tools import clean_response, clean_structure
from mango.tools import get_search_item, get_search_list
from tornado import httpclient as _http_client

_http_client.AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient')
http_client = _http_client.AsyncHTTPClient()

class TeamsResult(BaseResult):
    '''
        List result
    '''
    results = compound.ListType(compound.ModelType(teams.Team))

class Teams(object):
    '''
        Teams
    '''
    @gen.coroutine
    def get_team(self, account, team_uuid):
        '''
            Get team
        '''
        search_index = 'mango_team_index'
        query = 'uuid_register:{0}'.format(team_uuid)
        filter_query = 'account_register:{0}'.format(account.decode('utf-8'))
        # note where the hack change ' to %27 for the url string!
        fq_watchers = "watchers_register:*'{0}'*".format(account.decode('utf8')).replace("'",'%27')
        urls = set()
        urls.add(get_search_item(self.solr, search_index, query, filter_query))
        urls.add(get_search_item(self.solr, search_index, query, fq_watchers))
        # init got response list
        got_response = []
        # init crash message
        message = {'message': 'not found'}
        # ignore riak fields
        IGNORE_ME = ["_yz_id","_yz_rk","_yz_rt","_yz_rb"]
        # hopefully asynchronous handle function request
        def handle_request(response):
            '''
                Request Async Handler
            '''
            if response.error:
                logging.error(response.error)
                got_response.append({'error':True, 'message': response.error})
            else:
                got_response.append(json.loads(response.body))
        try:
            # and know for something completly different!
            for url in urls:
                http_client.fetch(
                    url,
                    callback=handle_request
                )
            while len(got_response) <= 1:
                # Yo, don't be careless with the time!
                yield gen.sleep(0.0010)
            # get stuff from response
            stuff = got_response[0]
            # get it from watchers list
            watchers = got_response[1]
            if stuff['response']['numFound']:
                response = stuff['response']['docs'][0]
                message = clean_response(response, IGNORE_ME)
            elif watchers['response']['numFound']:
                response = watchers['response']['docs'][0]
                message = clean_response(response, IGNORE_ME)
            else:
                logging.error('there is probably something wrong!')
        except Exception as error:
            logging.warning(error)
        return message

    @gen.coroutine
    def get_team_list(self, account, start, end, lapse, status, page_num):
        '''
            Get team list
        '''
        search_index = 'mango_team_index'
        query = 'uuid_register:*'
        filter_query = 'account_register:{0}'.format(account.decode('utf-8'))
        # note where the hack change ' to %27 for the url string!
        fq_watchers = "watchers_register:*'{0}'*".format(account.decode('utf8')).replace("'",'%27')
        # page number
        page_num = int(page_num)
        page_size = self.settings['page_size']
        start_num = page_size * (page_num - 1)
        # set of urls
        urls = set()
        urls.add(get_search_list(self.solr, search_index, query, filter_query, start_num, page_size))
        urls.add(get_search_list(self.solr, search_index, query, fq_watchers, start_num, page_size))
        # init got response list
        got_response = []
        # init crash message
        message = {
            'count': 0,
            'page': page_num,
            'results': []
        }
        # ignore riak fields
        IGNORE_ME = ["_yz_id","_yz_rk","_yz_rt","_yz_rb"]
        # hopefully asynchronous handle function request
        def handle_request(response):
            '''
                Request Async Handler
            '''
            if response.error:
                logging.error(response.error)
                got_response.append({'error':True, 'message': response.error})
            else:
                got_response.append(json.loads(response.body))
        try:
            # and know for something completly different!
            for url in urls:
                http_client.fetch(
                    url,
                    callback=handle_request
                )
            while len(got_response) <= 1:
                # Yo, don't be careless with the time!
                yield gen.sleep(0.0010)
            # get stuff from response
            stuff = got_response[0]
            # get it from watchers list
            watchers = got_response[1]
            if stuff['response']['numFound']:
                message['count'] += stuff['response']['numFound']
                for doc in stuff['response']['docs']:
                    message['results'].append(clean_response(doc, IGNORE_ME))
            if watchers['response']['numFound']:
                message['count'] += watchers['response']['numFound']
                for doc in watchers['response']['docs']:
                    message['results'].append(clean_response(doc, IGNORE_ME))
            else:
                logging.error('there is probably something wrong!')
        except Exception as error:
            logging.warning(error)
        return message

    @gen.coroutine
    def add_team(self, org_account, org_uuid, team_uuid):
        '''
            add (ORG) team
        '''
        # pretty please, dont hardcode your shitty domain in here.
        url = 'https://nonsense.ws/orgs/{0}'.format(org_uuid)
        # got callback response?
        got_response = []
        # yours truly
        headers = {'content-type':'application/json'}
        # and know for something completly different
        message = {
            'account': org_account,
            'teams':[team_uuid]
        }
        def handle_request(response):
            '''
                Request Async Handler
            '''
            if response.error:
                logging.error(response.error)
                got_response.append({'error':True, 'message': response.error})
            else:
                got_response.append(json.loads(response.body))
        try:
            http_client.fetch(
                url,
                method='PATCH',
                headers=headers,
                body=json.dumps(message),
                callback=handle_request
            )
            while len(got_response) == 0:
                # don't be careless with the time.
                yield gen.sleep(0.0010)
            logging.warning(got_response)
            #response = got_response[0].get('response')['docs'][0]
        except Exception as error:
            logging.error(error)
            message = str(error)
        return message

    @gen.coroutine
    def new_team(self, struct):
        '''
            New team event
        '''
        search_index = 'mango_team_index'
        bucket_type = 'mango_team'
        bucket_name = 'teams'
        try:
            event = teams.Team(struct)
            event.validate()
            event = clean_structure(event)
        except Exception as error:
            raise error
        try:
            message = event.get('uuid')
            structure = {
                "uuid": str(event.get('uuid', str(uuid.uuid4()))),
                "account": str(event.get('account', 'pebkac')),
                "status": str(event.get('status', '')),
                "name": str(event.get('name', '')),
                "permission": str(event.get('permission', '')),
                "members": str(event.get('members', '')),
                "resources": str(event.get('resources', '')),
                "labels": str(event.get('labels', '')),
                "history": str(event.get('history', '')),
                "checked": str(event.get('checked', '')),
                "checked_by": str(event.get('checked_by', '')),
                "checked_at": str(event.get('checked_at', '')),
                "created_by": str(event.get('created_by', '')),
                "created_at": str(event.get('created_at', '')),
                "last_update_by": str(event.get('last_update_by', '')),
                "last_update_at": str(event.get('last_update_at', '')),
            }
            result = TeamMap(
                self.kvalue,
                bucket_name,
                bucket_type,
                search_index,
                structure
            )
            message = structure.get('uuid')
        except Exception as error:
            logging.error(error)
            message = str(error)
        return message

    @gen.coroutine
    def modify_team(self, account, team_uuid, struct):
        '''
            Modify team
        '''
        # riak search index
        search_index = 'mango_team_index'
        # riak bucket type
        bucket_type = 'mango_team'
        # riak bucket name
        bucket_name = 'teams'
        # solr query
        query = 'uuid_register:{0}'.format(team_uuid.rstrip('/'))
        # filter query
        filter_query = 'account_register:{0}'.format(account.decode('utf-8'))
        # search query url
        url = "https://{0}/search/query/{1}?wt=json&q={2}&fq={3}".format(
            self.solr, search_index, query, filter_query
        )
        # pretty please, ignore this list of fields from database.
        IGNORE_ME = ("_yz_id","_yz_rk","_yz_rt","_yz_rb","checked","keywords")
        # got callback response?
        got_response = []
        # yours truly
        message = {'update_complete':False}
        def handle_request(response):
            '''
                Request Async Handler
            '''
            if response.error:
                logging.error(response.error)
                got_response.append({'error':True, 'message': response.error})
            else:
                got_response.append(json.loads(response.body))
        try:
            http_client.fetch(
                url,
                callback=handle_request
            )
            while len(got_response) == 0:
                # don't be careless with the time.
                yield gen.sleep(0.0010)
            response = got_response[0].get('response')['docs'][0]
            riak_key = str(response['_yz_rk'])
            bucket = self.kvalue.bucket_type(bucket_type).bucket('{0}'.format(bucket_name))
            bucket.set_properties({'search_index': search_index})
            team = Map(bucket, riak_key)
            for key in struct:
                if key not in IGNORE_ME:
                    if type(struct.get(key)) == list:
                        team.reload()
                        old_value = team.registers['{0}'.format(key)].value
                        if old_value:
                            old_list = json.loads(old_value.replace("'",'"'))
                            for thing in struct.get(key):
                                old_list.append(thing)
                            team.registers['{0}'.format(key)].assign(str(old_list))
                        else:
                            new_list = []
                            for thing in struct.get(key):
                                new_list.append(thing)
                            team.registers['{0}'.format(key)].assign(str(new_list))
                    else:
                        team.registers['{0}'.format(key)].assign(str(struct.get(key)))
                    team.update()
            update_complete = True
            message['update_complete'] = True
        except Exception as error:
            logging.exception(error)
        return message.get('update_complete', False)

    @gen.coroutine
    def modify_remove(self, account, team_uuid, struct):
        '''
            Modify remove
        '''
        # riak search index
        search_index = 'mango_team_index'
        # riak bucket type
        bucket_type = 'mango_team'
        # riak bucket name
        bucket_name = 'teams'
        # solr query
        query = 'uuid_register:{0}'.format(team_uuid.rstrip('/'))
        # filter query
        filter_query = 'account_register:{0}'.format(account.decode('utf-8'))
        # search query url
        url = "https://{0}/search/query/{1}?wt=json&q={2}&fq={3}".format(
            self.solr, search_index, query, filter_query
        )
        # pretty please, ignore this list of fields from database.
        IGNORE_ME = ("_yz_id","_yz_rk","_yz_rt","_yz_rb","checked","keywords")
        # got callback response?
        got_response = []
        # yours truly
        message = {'update_complete':False}
        def handle_request(response):
            '''
                Request Async Handler
            '''
            if response.error:
                logging.error(response.error)
                got_response.append({'error':True, 'message': response.error})
            else:
                got_response.append(json.loads(response.body))
        try:
            http_client.fetch(
                url,
                callback=handle_request
            )
            while len(got_response) == 0:
                # Please, don't be careless with the time.
                yield gen.sleep(0.0010)
            response = got_response[0].get('response')['docs'][0]
            riak_key = str(response['_yz_rk'])
            bucket = self.kvalue.bucket_type(bucket_type).bucket('{0}'.format(bucket_name))
            bucket.set_properties({'search_index': search_index})
            team = Map(bucket, riak_key)
            for key in struct:
                if key not in IGNORE_ME:
                    if type(struct.get(key)) == list:
                        team.reload()
                        old_value = team.registers['{0}'.format(key)].value
                        if old_value:
                            old_list = json.loads(old_value.replace("'",'"'))
                            new_list = [x for x in old_list if x not in struct.get(key)]
                            team.registers['{0}'.format(key)].assign(str(new_list))
                            team.update()
                            message['update_complete'] = True
                    else:
                        message['update_complete'] = False
        except Exception as error:
            logging.exception(error)
        return message.get('update_complete', False)

    @gen.coroutine
    def remove_team(self, account, team_uuid):
        '''
            Remove team
        '''
        # Yo, missing history ?
        struct = {}
        struct['status'] = 'deleted'
        message = yield self.modify_team(account, team_uuid, struct)
        return message
<commit_msg>clean, clean, clean<commit_after># -*- coding: utf-8 -*-
'''
    Mango teams system logic.
'''

# This file is part of mango.

# Distributed under the terms of the last AGPL License.
# The full license is in the file LICENCE, distributed as part of this software.

__author__ = 'Team Machine'

import uuid
import logging
import ujson as json
from tornado import gen
from schematics.types import compound
from mango.messages import teams
from mango.messages import BaseResult
from mango.structures.teams import TeamMap
from riak.datatypes import Map
from mango.tools import clean_response, clean_structure
from mango.tools import get_search_item, get_search_list
from tornado import httpclient as _http_client

_http_client.AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient')
http_client = _http_client.AsyncHTTPClient()

class TeamsResult(BaseResult):
    '''
        List result
    '''
    results = compound.ListType(compound.ModelType(teams.Team))

class Teams(object):
    '''
        Teams
    '''
    @gen.coroutine
    def get_team(self, account, team_uuid):
        '''
            Get team
        '''
        search_index = 'mango_team_index'
        query = 'uuid_register:{0}'.format(team_uuid)
        filter_query = 'account_register:{0}'.format(account.decode('utf-8'))
        # note where the hack change ' to %27 for the url string!
        fq_watchers = "watchers_register:*'{0}'*".format(account.decode('utf8')).replace("'",'%27')
        urls = set()
        urls.add(get_search_item(self.solr, search_index, query, filter_query))
        urls.add(get_search_item(self.solr, search_index, query, fq_watchers))
        # init got response list
        got_response = []
        # init crash message
        message = {'message': 'not found'}
        # ignore riak fields
        IGNORE_ME = ["_yz_id","_yz_rk","_yz_rt","_yz_rb"]
        # hopefully asynchronous handle function request
        def handle_request(response):
            '''
                Request Async Handler
            '''
            if response.error:
                logging.error(response.error)
                got_response.append({'error':True, 'message': response.error})
            else:
                got_response.append(json.loads(response.body))
        try:
            # and know for something completly different!
            for url in urls:
                http_client.fetch(
                    url,
                    callback=handle_request
                )
            while len(got_response) <= 1:
                # Yo, don't be careless with the time!
                yield gen.sleep(0.0010)
            # get stuff from response
            stuff = got_response[0]
            # get it from watchers list
            watchers = got_response[1]
            if stuff['response']['numFound']:
                response = stuff['response']['docs'][0]
                message = clean_response(response, IGNORE_ME)
            elif watchers['response']['numFound']:
                response = watchers['response']['docs'][0]
                message = clean_response(response, IGNORE_ME)
            else:
                logging.error('there is probably something wrong!')
        except Exception as error:
            logging.warning(error)
        return message

    @gen.coroutine
    def get_team_list(self, account, start, end, lapse, status, page_num):
        '''
            Get team list
        '''
        search_index = 'mango_team_index'
        query = 'uuid_register:*'
        filter_query = 'account_register:{0}'.format(account.decode('utf-8'))
        # note where the hack change ' to %27 for the url string!
        fq_watchers = "watchers_register:*'{0}'*".format(account.decode('utf8')).replace("'",'%27')
        # page number
        page_num = int(page_num)
        page_size = self.settings['page_size']
        start_num = page_size * (page_num - 1)
        # set of urls
        urls = set()
        urls.add(get_search_list(self.solr, search_index, query, filter_query, start_num, page_size))
        urls.add(get_search_list(self.solr, search_index, query, fq_watchers, start_num, page_size))
        # init got response list
        got_response = []
        # init crash message
        message = {
            'count': 0,
            'page': page_num,
            'results': []
        }
        # ignore riak fields
        IGNORE_ME = ["_yz_id","_yz_rk","_yz_rt","_yz_rb"]
        # hopefully asynchronous handle function request
        def handle_request(response):
            '''
                Request Async Handler
            '''
            if response.error:
                logging.error(response.error)
                got_response.append({'error':True, 'message': response.error})
            else:
                got_response.append(json.loads(response.body))
        try:
            # and know for something completly different!
            for url in urls:
                http_client.fetch(
                    url,
                    callback=handle_request
                )
            while len(got_response) <= 1:
                # Yo, don't be careless with the time!
                yield gen.sleep(0.0010)
            # get stuff from response
            stuff = got_response[0]
            # get it from watchers list
            watchers = got_response[1]
            if stuff['response']['numFound']:
                message['count'] += stuff['response']['numFound']
                for doc in stuff['response']['docs']:
                    message['results'].append(clean_response(doc, IGNORE_ME))
            if watchers['response']['numFound']:
                message['count'] += watchers['response']['numFound']
                for doc in watchers['response']['docs']:
                    message['results'].append(clean_response(doc, IGNORE_ME))
            else:
                logging.error('there is probably something wrong!')
        except Exception as error:
            logging.warning(error)
        return message

    @gen.coroutine
    def add_team(self, org_account, org_uuid, team_uuid):
        '''
            add (ORG) team
        '''
        # please, dont hardcode your shitty domain in here.
        url = 'https://nonsense.ws/orgs/{0}'.format(org_uuid)
        # got callback response?
        got_response = []
        # yours truly
        headers = {'content-type':'application/json'}
        # and know for something completly different
        message = {
            'account': org_account,
            'teams':[team_uuid]
        }
        def handle_request(response):
            '''
                Request Async Handler
            '''
            if response.error:
                logging.error(response.error)
                got_response.append({'error':True, 'message': response.error})
            else:
                got_response.append(json.loads(response.body))
        try:
            http_client.fetch(
                url,
                method='PATCH',
                headers=headers,
                body=json.dumps(message),
                callback=handle_request
            )
            while len(got_response) == 0:
                # don't be careless with the time.
                yield gen.sleep(0.0010)
            logging.warning(got_response)
            #response = got_response[0].get('response')['docs'][0]
        except Exception as error:
            logging.error(error)
            message = str(error)
        return message

    @gen.coroutine
    def new_team(self, struct):
        '''
            New team event
        '''
        search_index = 'mango_team_index'
        bucket_type = 'mango_team'
        bucket_name = 'teams'
        try:
            event = teams.Team(struct)
            event.validate()
            event = clean_structure(event)
        except Exception as error:
            raise error
        try:
            message = event.get('uuid')
            structure = {
                "uuid": str(event.get('uuid', str(uuid.uuid4()))),
                "account": str(event.get('account', 'pebkac')),
                "status": str(event.get('status', '')),
                "name": str(event.get('name', '')),
                "permission": str(event.get('permission', '')),
                "members": str(event.get('members', '')),
                "resources": str(event.get('resources', '')),
                "labels": str(event.get('labels', '')),
                "history": str(event.get('history', '')),
                "checked": str(event.get('checked', '')),
                "checked_by": str(event.get('checked_by', '')),
                "checked_at": str(event.get('checked_at', '')),
                "created_by": str(event.get('created_by', '')),
                "created_at": str(event.get('created_at', '')),
                "last_update_by": str(event.get('last_update_by', '')),
                "last_update_at": str(event.get('last_update_at', '')),
            }
            result = TeamMap(
                self.kvalue,
                bucket_name,
                bucket_type,
                search_index,
                structure
            )
            message = structure.get('uuid')
        except Exception as error:
            logging.error(error)
            message = str(error)
        return message

    @gen.coroutine
    def modify_team(self, account, team_uuid, struct):
        '''
            Modify team
        '''
        # riak search index
        search_index = 'mango_team_index'
        # riak bucket type
        bucket_type = 'mango_team'
        # riak bucket name
        bucket_name = 'teams'
        # solr query
        query = 'uuid_register:{0}'.format(team_uuid.rstrip('/'))
        # filter query
        filter_query = 'account_register:{0}'.format(account.decode('utf-8'))
        # search query url
        url = "https://{0}/search/query/{1}?wt=json&q={2}&fq={3}".format(
            self.solr, search_index, query, filter_query
        )
        # pretty please, ignore this list of fields from database.
        IGNORE_ME = ("_yz_id","_yz_rk","_yz_rt","_yz_rb","checked","keywords")
        # got callback response?
        got_response = []
        # yours truly
        message = {'update_complete':False}
        def handle_request(response):
            '''
                Request Async Handler
            '''
            if response.error:
                logging.error(response.error)
                got_response.append({'error':True, 'message': response.error})
            else:
                got_response.append(json.loads(response.body))
        try:
            http_client.fetch(
                url,
                callback=handle_request
            )
            while len(got_response) == 0:
                # don't be careless with the time.
                yield gen.sleep(0.0010)
            response = got_response[0].get('response')['docs'][0]
            riak_key = str(response['_yz_rk'])
            bucket = self.kvalue.bucket_type(bucket_type).bucket('{0}'.format(bucket_name))
            bucket.set_properties({'search_index': search_index})
            team = Map(bucket, riak_key)
            for key in struct:
                if key not in IGNORE_ME:
                    if type(struct.get(key)) == list:
                        team.reload()
                        old_value = team.registers['{0}'.format(key)].value
                        if old_value:
                            old_list = json.loads(old_value.replace("'",'"'))
                            for thing in struct.get(key):
                                old_list.append(thing)
                            team.registers['{0}'.format(key)].assign(str(old_list))
                        else:
                            new_list = []
                            for thing in struct.get(key):
                                new_list.append(thing)
                            team.registers['{0}'.format(key)].assign(str(new_list))
                    else:
                        team.registers['{0}'.format(key)].assign(str(struct.get(key)))
                    team.update()
            update_complete = True
            message['update_complete'] = True
        except Exception as error:
            logging.exception(error)
        return message.get('update_complete', False)

    @gen.coroutine
    def modify_remove(self, account, team_uuid, struct):
        '''
            Modify remove
        '''
        # riak search index
        search_index = 'mango_team_index'
        # riak bucket type
        bucket_type = 'mango_team'
        # riak bucket name
        bucket_name = 'teams'
        # solr query
        query = 'uuid_register:{0}'.format(team_uuid.rstrip('/'))
        # filter query
        filter_query = 'account_register:{0}'.format(account.decode('utf-8'))
        # search query url
        url = "https://{0}/search/query/{1}?wt=json&q={2}&fq={3}".format(
            self.solr, search_index, query, filter_query
        )
        # pretty please, ignore this list of fields from database.
        IGNORE_ME = ("_yz_id","_yz_rk","_yz_rt","_yz_rb","checked","keywords")
        # got callback response?
        got_response = []
        # yours truly
        message = {'update_complete':False}
        def handle_request(response):
            '''
                Request Async Handler
            '''
            if response.error:
                logging.error(response.error)
                got_response.append({'error':True, 'message': response.error})
            else:
                got_response.append(json.loads(response.body))
        try:
            http_client.fetch(
                url,
                callback=handle_request
            )
            while len(got_response) == 0:
                # Please, don't be careless with the time.
                yield gen.sleep(0.0010)
            response = got_response[0].get('response')['docs'][0]
            riak_key = str(response['_yz_rk'])
            bucket = self.kvalue.bucket_type(bucket_type).bucket('{0}'.format(bucket_name))
            bucket.set_properties({'search_index': search_index})
            team = Map(bucket, riak_key)
            for key in struct:
                if key not in IGNORE_ME:
                    if type(struct.get(key)) == list:
                        team.reload()
                        old_value = team.registers['{0}'.format(key)].value
                        if old_value:
                            old_list = json.loads(old_value.replace("'",'"'))
                            new_list = [x for x in old_list if x not in struct.get(key)]
                            team.registers['{0}'.format(key)].assign(str(new_list))
                            team.update()
                            message['update_complete'] = True
                    else:
                        message['update_complete'] = False
        except Exception as error:
            logging.exception(error)
        return message.get('update_complete', False)

    @gen.coroutine
    def remove_team(self, account, team_uuid):
        '''
            Remove team
        '''
        # Yo, missing history ?
        struct = {}
        struct['status'] = 'deleted'
        message = yield self.modify_team(account, team_uuid, struct)
        return message
<|endoftext|>


<commit_before># coding=utf-8
from sii import models, __SII_VERSION__
from datetime import datetime

def get_iva_values(tax_line):
    vals = {
        'sujeta_a_iva': False
    }
    for tax in tax_line:
        if 'IVA' in tax.name:
            vals.update({
                'sujeta_a_iva': True,
                'base_imponible': tax.base,
                'tipo_impositivo': tax.tax_id.amount * 100,
                'cuota_repercutida': tax.tax_amount
            })
            break
    return vals

def get_factura_emitida(invoice):
    vals = get_iva_values(invoice.tax_line)

    if vals['sujeta_a_iva']:
        tipo_desglose = {
            'DesgloseFactura': {
                'Sujeta': {
                    'NoExenta': {  # TODO Exenta o no exenta??
                        'TipoNoExenta': 'S1',
                        'DesgloseIVA': {
                            'DetalleIVA': {
                                'TipoImpositivo': vals['tipo_impositivo'],
                                'BaseImponible': vals['base_imponible'],
                                'CuotaRepercutida': vals['cuota_repercutida']
                            }
                        }
                    }
                }
            }
        }
    else:
        tipo_desglose = {
            'DesgloseFactura': {
                'NoSujeta': ''
            }
        }

    factura_expedida = {
        'TipoFactura': 'F1',
        'ClaveRegimenEspecialOTrascendencia': '01',  # TODO
        'ImporteTotal': invoice.amount_total,
        'DescripcionOperacion': invoice.name,
        'Contraparte': {
            'NombreRazon': invoice.partner_id.name,
            'NIF': invoice.partner_id.vat
        },
        'TipoDesglose': tipo_desglose
    }

    return factura_expedida

def get_factura_recibida(invoice):
    vals = get_iva_values(invoice.tax_line)

    if vals['sujeta_a_iva']:
        tipo_desglose = {
            'InversionSujetoPasivo': {
                'DetalleIVA': {
                    'TipoImpositivo': vals['tipo_impositivo'],
                    'BaseImponible': vals['base_imponible'],
                    'CuotaRepercutida': vals['cuota_repercutida']
                }
            },
            'DesgloseIVA': {
                'DetalleIVA': {
                    'BaseImponible': vals['base_imponible']
                }
            }
        }
    else:
        raise Exception("Missing 'IVA' in invoice.tax_line")

    factura_recibida = {
        'TipoFactura': 'F1',
        'ClaveRegimenEspecialOTrascendencia': '01',  # TODO
        'ImporteTotal': invoice.amount_total,
        'DescripcionOperacion': invoice.name,
        'Contraparte': {
            'NombreRazon': invoice.partner_id.name,
            'NIF': invoice.partner_id.vat
        },
        'DesgloseFactura': tipo_desglose,
        'CuotaDeducible': vals['cuota_repercutida'],
        'FechaRegContable': ''  # TODO to change
    }

    return factura_recibida

def get_header(invoice):
    cabecera = {
        'IDVersionSii': __SII_VERSION__,
        'Titular': {
            'NombreRazon': invoice.company_id.partner_id.name,
            'NIF': invoice.company_id.partner_id.vat
        },
        'TipoComunicacion': 'A0'
    }

    return cabecera

def get_factura_rectificativa_fields():
    rectificativa_fields = {
        'TipoRectificativa': 'S',  # Por sustitución
        'ImporteRectificacion': {
            'BaseRectificada': 0,
            'CuotaRectificada': 0
        }
    }

    return rectificativa_fields

def get_factura_emitida_dict(invoice, rectificativa=False):
    obj = {
        'SuministroLRFacturasEmitidas': {
            'Cabecera': get_header(invoice),
            'RegistroLRFacturasEmitidas': {
                'PeriodoImpositivo': {
                    'Ejercicio': invoice.period_id.name[3:7],
                    'Periodo': invoice.period_id.name[0:2]
                },
                'IDFactura': {
                    'IDEmisorFactura': {
                        'NIF': invoice.company_id.partner_id.vat
                    },
                    'NumSerieFacturaEmisor': invoice.number,
                    'FechaExpedicionFacturaEmisor': datetime.strptime(
                        invoice.date_invoice, '%Y-%m-%d'
                    ).strftime('%d-%m-%Y')
                },
                'FacturaExpedida': get_factura_emitida(invoice)
            }
        }
    }

    if rectificativa:
        vals = get_factura_rectificativa_fields()

        obj['SuministroLRFacturasEmitidas']['RegistroLRFacturasEmitidas']['FacturaExpedida'].update(vals)

    return obj

def get_factura_recibida_dict(invoice, rectificativa=False):
    obj = {
        'SuministroLRFacturasRecibidas': {
            'Cabecera': get_header(invoice),
            'RegistroLRFacturasRecibidas': {
                'PeriodoImpositivo': {
                    'Ejercicio': invoice.period_id.name[3:7],
                    'Periodo': invoice.period_id.name[0:2]
                },
                'IDFactura': {
                    'IDEmisorFactura': {
                        'NIF': invoice.partner_id.vat
                    },
                    'NumSerieFacturaEmisor': invoice.number,
                    'FechaExpedicionFacturaEmisor': datetime.strptime(
                        invoice.date_invoice, '%Y-%m-%d'
                    ).strftime('%d-%m-%Y')
                },
                'FacturaRecibida': get_factura_recibida(invoice)
            }
        }
    }

    if rectificativa:
        vals = get_factura_rectificativa_fields()

        obj['SuministroLRFacturasRecibidas']['RegistroLRFacturasRecibidas']['FacturaRecibida'].update(vals)

    return obj

class SII(object):
    @staticmethod
    def generate_object(invoice):

        if invoice.type == 'in_invoice':
            invoice_model = models.SuministroFacturasRecibidas()
            invoice_dict = get_factura_recibida_dict(invoice)
        elif invoice.type == 'out_invoice':
            invoice_model = models.SuministroFacturasEmitidas()
            invoice_dict = get_factura_emitida_dict(invoice)
        elif invoice.type == 'in_refund':
            invoice_model = models.SuministroFacturasRecibidas()
            invoice_dict = get_factura_recibida_dict(invoice, rectificativa=True)
        elif invoice.type == 'out_refund':
            invoice_model = models.SuministroFacturasEmitidas()
            invoice_dict = get_factura_emitida_dict(invoice, rectificativa=True)
        else:
            raise Exception('Error in invoice.type')

        errors = invoice_model.validate(invoice_dict)
        if errors:
            raise Exception(
                'Errors were found while trying to generate the dump:', errors)

        res = invoice_model.dump(invoice_dict).data
        return res
<commit_msg>Remove datetime library usages<commit_after># coding=utf-8
from sii import models, __SII_VERSION__

def get_iva_values(tax_line):
    vals = {
        'sujeta_a_iva': False
    }
    for tax in tax_line:
        if 'IVA' in tax.name:
            vals.update({
                'sujeta_a_iva': True,
                'base_imponible': tax.base,
                'tipo_impositivo': tax.tax_id.amount * 100,
                'cuota_repercutida': tax.tax_amount
            })
            break
    return vals

def get_factura_emitida(invoice):
    vals = get_iva_values(invoice.tax_line)

    if vals['sujeta_a_iva']:
        tipo_desglose = {
            'DesgloseFactura': {
                'Sujeta': {
                    'NoExenta': {  # TODO Exenta o no exenta??
                        'TipoNoExenta': 'S1',
                        'DesgloseIVA': {
                            'DetalleIVA': {
                                'TipoImpositivo': vals['tipo_impositivo'],
                                'BaseImponible': vals['base_imponible'],
                                'CuotaRepercutida': vals['cuota_repercutida']
                            }
                        }
                    }
                }
            }
        }
    else:
        tipo_desglose = {
            'DesgloseFactura': {
                'NoSujeta': ''
            }
        }

    factura_expedida = {
        'TipoFactura': 'F1',
        'ClaveRegimenEspecialOTrascendencia': '01',  # TODO
        'ImporteTotal': invoice.amount_total,
        'DescripcionOperacion': invoice.name,
        'Contraparte': {
            'NombreRazon': invoice.partner_id.name,
            'NIF': invoice.partner_id.vat
        },
        'TipoDesglose': tipo_desglose
    }

    return factura_expedida

def get_factura_recibida(invoice):
    vals = get_iva_values(invoice.tax_line)

    if vals['sujeta_a_iva']:
        tipo_desglose = {
            'InversionSujetoPasivo': {
                'DetalleIVA': {
                    'TipoImpositivo': vals['tipo_impositivo'],
                    'BaseImponible': vals['base_imponible'],
                    'CuotaRepercutida': vals['cuota_repercutida']
                }
            },
            'DesgloseIVA': {
                'DetalleIVA': {
                    'BaseImponible': vals['base_imponible']
                }
            }
        }
    else:
        raise Exception("Missing 'IVA' in invoice.tax_line")

    factura_recibida = {
        'TipoFactura': 'F1',
        'ClaveRegimenEspecialOTrascendencia': '01',  # TODO
        'ImporteTotal': invoice.amount_total,
        'DescripcionOperacion': invoice.name,
        'Contraparte': {
            'NombreRazon': invoice.partner_id.name,
            'NIF': invoice.partner_id.vat
        },
        'DesgloseFactura': tipo_desglose,
        'CuotaDeducible': vals['cuota_repercutida'],
        'FechaRegContable': ''  # TODO to change
    }

    return factura_recibida

def get_header(invoice):
    cabecera = {
        'IDVersionSii': __SII_VERSION__,
        'Titular': {
            'NombreRazon': invoice.company_id.partner_id.name,
            'NIF': invoice.company_id.partner_id.vat
        },
        'TipoComunicacion': 'A0'
    }

    return cabecera

def get_factura_rectificativa_fields():
    rectificativa_fields = {
        'TipoRectificativa': 'S',  # Por sustitución
        'ImporteRectificacion': {
            'BaseRectificada': 0,
            'CuotaRectificada': 0
        }
    }

    return rectificativa_fields

def get_factura_emitida_dict(invoice, rectificativa=False):
    obj = {
        'SuministroLRFacturasEmitidas': {
            'Cabecera': get_header(invoice),
            'RegistroLRFacturasEmitidas': {
                'PeriodoImpositivo': {
                    'Ejercicio': invoice.period_id.name[3:7],
                    'Periodo': invoice.period_id.name[0:2]
                },
                'IDFactura': {
                    'IDEmisorFactura': {
                        'NIF': invoice.company_id.partner_id.vat
                    },
                    'NumSerieFacturaEmisor': invoice.number,
                    'FechaExpedicionFacturaEmisor': invoice.date_invoice
                },
                'FacturaExpedida': get_factura_emitida(invoice)
            }
        }
    }

    if rectificativa:
        vals = get_factura_rectificativa_fields()

        obj['SuministroLRFacturasEmitidas']['RegistroLRFacturasEmitidas']['FacturaExpedida'].update(vals)

    return obj

def get_factura_recibida_dict(invoice, rectificativa=False):
    obj = {
        'SuministroLRFacturasRecibidas': {
            'Cabecera': get_header(invoice),
            'RegistroLRFacturasRecibidas': {
                'PeriodoImpositivo': {
                    'Ejercicio': invoice.period_id.name[3:7],
                    'Periodo': invoice.period_id.name[0:2]
                },
                'IDFactura': {
                    'IDEmisorFactura': {
                        'NIF': invoice.partner_id.vat
                    },
                    'NumSerieFacturaEmisor': invoice.number,
                    'FechaExpedicionFacturaEmisor': invoice.date_invoice
                },
                'FacturaRecibida': get_factura_recibida(invoice)
            }
        }
    }

    if rectificativa:
        vals = get_factura_rectificativa_fields()

        obj['SuministroLRFacturasRecibidas']['RegistroLRFacturasRecibidas']['FacturaRecibida'].update(vals)

    return obj

class SII(object):
    @staticmethod
    def generate_object(invoice):

        if invoice.type == 'in_invoice':
            invoice_model = models.SuministroFacturasRecibidas()
            invoice_dict = get_factura_recibida_dict(invoice)
        elif invoice.type == 'out_invoice':
            invoice_model = models.SuministroFacturasEmitidas()
            invoice_dict = get_factura_emitida_dict(invoice)
        elif invoice.type == 'in_refund':
            invoice_model = models.SuministroFacturasRecibidas()
            invoice_dict = get_factura_recibida_dict(invoice, rectificativa=True)
        elif invoice.type == 'out_refund':
            invoice_model = models.SuministroFacturasEmitidas()
            invoice_dict = get_factura_emitida_dict(invoice, rectificativa=True)
        else:
            raise Exception('Error in invoice.type')

        errors = invoice_model.validate(invoice_dict)
        if errors:
            raise Exception(
                'Errors were found while trying to generate the dump:', errors)

        res = invoice_model.dump(invoice_dict).data
        return res
<|endoftext|>


<commit_before>"""
Module responsible for possible world and descriptive sentence handling.
"""

from judged import bdd
from judged import interned
from judged import formatting

class Sentence:
    def __format__(self, format_spec):
        return formatting.sentence(self, format_spec)

    def __repr__(self):
        return str(self)

    def create_bdd(self):
        raise NotImplementedError

    def labels(self):
        return set()

    def evaluate(self, checker):
        raise NotImplementedError

    def is_grounded(self):
        return True

    def subst(self):
        raise NotImplementedError

class Nary(Sentence):
    def __init__(self, *terms):
        self.terms = list(terms)

    def nary2string(self, opstr):
        if len(self.terms) == 1:
            return str(self.terms[0])
        terms = []
        for t in self.terms:
            terms.append(str(t))
        return '(' + (' ' + opstr + ' ').join(terms) + ')'

    def labels(self):
        return {e for t in self.terms for e in t.labels()}

    def is_grounded(self):
        return all(t.is_grounded() for t in self.terms)

    def subst(self, env):
        return type(self)(*[t.subst(env) for t in self.terms])

class Unary(Sentence):
    def __init__(self, sub):
        self.sub = sub

    def labels():
        return self.sub.labels()

    def is_grounded(self):
        return self.sub.is_grounded()

    def subst(self, env):
        return type(self)(self.sub.subst(env))

class Atom(Sentence, metaclass=interned.InternalizeMeta):
    pass

class Disjunction(Nary):
    def __str__(self):
        return self.nary2string("or")

    def create_bdd(self):
        res = bdd.constant(False)
        for s in self.terms:
            res = res | s.create_bdd()
        return res

    def evaluate(self, checker):
        for t in self.terms:
            if t.evaluate(checker) == True:
                return True
        return False

class Conjunction(Nary):
    def __str__(self):
        return self.nary2string("and")

    def create_bdd(self):
        res = bdd.constant(True)
        for s in self.terms:
            res = res & s.create_bdd()
        return res

    def evaluate(self, checker):
        for t in self.terms:
            if t.evaluate(checker) == False:
                return False
        return True

class Negation(Unary):
    def __str__(self):
        return "not {!s}".format(self.sub)

    def create_bdd(self):
        return ~ (self.sub.create_bdd())

    def evaluate(self, checker):
        return not self.sub.evaluate(checker)

    def labels(self):
        return self.sub.labels()

class Label(Atom):
    def __init__(self, partitioning, part):
        self.partitioning = partitioning
        self.part = part

    def __str__(self):
        return "{}={}".format(self.partitioning, self.part)

    def create_bdd(self):
        return mybddvar(self.partitioning.tag(), self.part.tag())

    def labels(self):
        return set([(self.partitioning, self.part)])

    def evaluate(self, checker):
        return checker(self.partitioning, self.part)

    def is_grounded(self):
        return self.partitioning.is_grounded() and self.part.is_grounded()

    def subst(self, env):
        return type(self)(self.partitioning.subst(env), self.part.subst(env))

class LabelFragment(metaclass=interned.InternalizeMeta):
    def is_grounded(self):
        raise NotImplementedError

class LabelConstant(LabelFragment):
    def __init__(self, constant):
        self.constant = constant

    def __str__(self):
        return str(self.constant)

    def is_grounded(self):
        return True

    def subst(self, env):
        return self

    def tag(self):
        return self.constant

class LabelFunction(LabelFragment):
    def __init__(self, name, terms):
        self.name = name
        self.terms = terms
        self._tag = None

    def __str__(self):
        return self.name + '(' +  ', '.join(str(t) for t in self.terms) + ')'

    def is_grounded(self):
        return all(t.is_const() for t in self.terms)

    def subst(self, env):
        if not env:
            return self

        terms = list(map(lambda t: t.subst(env), self.terms))
        return LabelFunction(self.name, tuple(terms))

    def tag(self):
        def add_size(string):
            return str(len(string)) + ':' + string
        result = self._tag
        if not result:
            result = add_size(self.name)
            for i in range(len(self.terms)):
                result += add_size(self.terms[i].id)
            self._tag = result
        return result

class Top(Atom):
    def __str__(self):
        return "true"

    def create_bdd(self):
        return bdd.constant(True)

    def evaluate(self, checker):
        return True

class Bottom(Atom):
    def __str__(self):
        return "false"

    def create_bdd(self):
        return bdd.constant(False)

    def evaluate(self, checker):
        return False

def mybddvar(p, i):
    """ helper function to ensure bddvars have same name everywhere """
    # TODO: Handle ungrounded variables
    return bdd.variable(str(p)+'_'+str(i))

def exclusion_matrix(partitions, kb):
    """
    Generates exclusion bdd's. So if xN has domain var[1,2] the following
    exclusion is generated: (x1 and not x2) or (x2 and not x1)
    """
    excl = None
    for key in partitions:
        group = kb.parts(key)
        if len(group) > 1:
            excl_sub = None
            excl_subsub = None
            for id in group:
                excl_subsub = mybddvar(key,id)
                for idnot in group:
                    if id != idnot:
                        excl_subsub = excl_subsub & ~ mybddvar(key,idnot)
                if excl_sub == None:
                    excl_sub = excl_subsub
                else:
                    excl_sub = excl_sub | excl_subsub
            if excl == None:
                excl = excl_sub
            else:
                excl = excl & excl_sub
    return excl

def equivalent(l, r, kb):
    """
    Determines if a descriptive sentence is equivalent to another, given the
    mutual exclusions from the given knowledge base.
    """
    # TODO: Maybe raise if l or r contains ungrounded variables?
    assert l.is_grounded() and r.is_grounded(), "cannot compare ungrounded sentences"

    lbdd = l.create_bdd()
    rbdd = r.create_bdd()

    excl = exclusion_matrix({t[0] for t in (l.labels() | r.labels())}, kb)
    if excl is not None:
        lbdd = lbdd & excl
        rbdd = rbdd & excl

    return lbdd == rbdd

def falsehood(s, kb):
    """
    Determines if a world is a contradiction, i.e., if it can only exist
    through a violation of a mutually exclusive labelling.
    """
    sbdd = s.create_bdd()

    excl = exclusion_matrix({t[0] for t in s.labels()}, kb)
    if excl is not None:
        sbdd = sbdd & excl

    return sbdd.is_zero()

def labels(s):
    return s.labels()

def evaluate(s, checker):
    return s.evaluate(checker)

def is_grounded(s):
    return s.is_grounded()

def subst(s, env):
    return s.subst(env)

def conjunct(*terms):
    used = {t for t in terms if t != Top()}
    if len(used) == 0:
        return Top()
    elif len(used) == 1:
        return used.pop()
    else:
        return Conjunction(*used)

def disjunct(*terms):
    used = {t for t in terms if t != Bottom()}
    if len(used) == 0:
        return Bottom()
    elif len(used) == 1:
        return used.pop()
    else:
        return Disjunction(*used)
<commit_msg>Remove checked todos and streamline add_size<commit_after>"""
Module responsible for possible world and descriptive sentence handling.
"""

from judged import bdd
from judged import interned
from judged import formatting

class Sentence:
    def __format__(self, format_spec):
        return formatting.sentence(self, format_spec)

    def __repr__(self):
        return str(self)

    def create_bdd(self):
        raise NotImplementedError

    def labels(self):
        return set()

    def evaluate(self, checker):
        raise NotImplementedError

    def is_grounded(self):
        return True

    def subst(self):
        raise NotImplementedError

class Nary(Sentence):
    def __init__(self, *terms):
        self.terms = list(terms)

    def nary2string(self, opstr):
        if len(self.terms) == 1:
            return str(self.terms[0])
        terms = []
        for t in self.terms:
            terms.append(str(t))
        return '(' + (' ' + opstr + ' ').join(terms) + ')'

    def labels(self):
        return {e for t in self.terms for e in t.labels()}

    def is_grounded(self):
        return all(t.is_grounded() for t in self.terms)

    def subst(self, env):
        return type(self)(*[t.subst(env) for t in self.terms])

class Unary(Sentence):
    def __init__(self, sub):
        self.sub = sub

    def labels():
        return self.sub.labels()

    def is_grounded(self):
        return self.sub.is_grounded()

    def subst(self, env):
        return type(self)(self.sub.subst(env))

class Atom(Sentence, metaclass=interned.InternalizeMeta):
    pass

class Disjunction(Nary):
    def __str__(self):
        return self.nary2string("or")

    def create_bdd(self):
        res = bdd.constant(False)
        for s in self.terms:
            res = res | s.create_bdd()
        return res

    def evaluate(self, checker):
        for t in self.terms:
            if t.evaluate(checker) == True:
                return True
        return False

class Conjunction(Nary):
    def __str__(self):
        return self.nary2string("and")

    def create_bdd(self):
        res = bdd.constant(True)
        for s in self.terms:
            res = res & s.create_bdd()
        return res

    def evaluate(self, checker):
        for t in self.terms:
            if t.evaluate(checker) == False:
                return False
        return True

class Negation(Unary):
    def __str__(self):
        return "not {!s}".format(self.sub)

    def create_bdd(self):
        return ~ (self.sub.create_bdd())

    def evaluate(self, checker):
        return not self.sub.evaluate(checker)

    def labels(self):
        return self.sub.labels()

class Label(Atom):
    def __init__(self, partitioning, part):
        self.partitioning = partitioning
        self.part = part

    def __str__(self):
        return "{}={}".format(self.partitioning, self.part)

    def create_bdd(self):
        return mybddvar(self.partitioning.tag(), self.part.tag())

    def labels(self):
        return set([(self.partitioning, self.part)])

    def evaluate(self, checker):
        return checker(self.partitioning, self.part)

    def is_grounded(self):
        return self.partitioning.is_grounded() and self.part.is_grounded()

    def subst(self, env):
        return type(self)(self.partitioning.subst(env), self.part.subst(env))

class LabelFragment(metaclass=interned.InternalizeMeta):
    @staticmethod
    def add_size(s):
        return str(len(s)) + ':' + s

    def is_grounded(self):
        raise NotImplementedError

class LabelConstant(LabelFragment):
    def __init__(self, constant):
        self.constant = constant

    def __str__(self):
        return str(self.constant)

    def is_grounded(self):
        return True

    def subst(self, env):
        return self

    def tag(self):
        return self.constant

class LabelFunction(LabelFragment):
    def __init__(self, name, terms):
        self.name = name
        self.terms = terms
        self._tag = None

    def __str__(self):
        return self.name + '(' +  ', '.join(str(t) for t in self.terms) + ')'

    def is_grounded(self):
        return all(t.is_const() for t in self.terms)

    def subst(self, env):
        if not env:
            return self

        terms = list(map(lambda t: t.subst(env), self.terms))
        return LabelFunction(self.name, tuple(terms))

    def tag(self):
        add_size = lambda s: str(len(s))+':'+s
        result = self._tag
        if not result:
            result = add_size(self.name)
            for i in range(len(self.terms)):
                result += add_size(self.terms[i].id)
            self._tag = result
        return result

class Top(Atom):
    def __str__(self):
        return "true"

    def create_bdd(self):
        return bdd.constant(True)

    def evaluate(self, checker):
        return True

class Bottom(Atom):
    def __str__(self):
        return "false"

    def create_bdd(self):
        return bdd.constant(False)

    def evaluate(self, checker):
        return False

def mybddvar(p, i):
    """ helper function to ensure bddvars have same name everywhere """
    return bdd.variable(str(p)+'_'+str(i))

def exclusion_matrix(partitions, kb):
    """
    Generates exclusion bdd's. So if xN has domain var[1,2] the following
    exclusion is generated: (x1 and not x2) or (x2 and not x1)
    """
    excl = None
    for key in partitions:
        group = kb.parts(key)
        if len(group) > 1:
            excl_sub = None
            excl_subsub = None
            for id in group:
                excl_subsub = mybddvar(key,id)
                for idnot in group:
                    if id != idnot:
                        excl_subsub = excl_subsub & ~ mybddvar(key,idnot)
                if excl_sub == None:
                    excl_sub = excl_subsub
                else:
                    excl_sub = excl_sub | excl_subsub
            if excl == None:
                excl = excl_sub
            else:
                excl = excl & excl_sub
    return excl

def equivalent(l, r, kb):
    """
    Determines if a descriptive sentence is equivalent to another, given the
    mutual exclusions from the given knowledge base.
    """
    assert l.is_grounded() and r.is_grounded(), "cannot compare ungrounded sentences"

    lbdd = l.create_bdd()
    rbdd = r.create_bdd()

    excl = exclusion_matrix({t[0] for t in (l.labels() | r.labels())}, kb)
    if excl is not None:
        lbdd = lbdd & excl
        rbdd = rbdd & excl

    return lbdd == rbdd

def falsehood(s, kb):
    """
    Determines if a world is a contradiction, i.e., if it can only exist
    through a violation of a mutually exclusive labelling.
    """
    sbdd = s.create_bdd()

    excl = exclusion_matrix({t[0] for t in s.labels()}, kb)
    if excl is not None:
        sbdd = sbdd & excl

    return sbdd.is_zero()

def labels(s):
    return s.labels()

def evaluate(s, checker):
    return s.evaluate(checker)

def is_grounded(s):
    return s.is_grounded()

def subst(s, env):
    return s.subst(env)

def conjunct(*terms):
    used = {t for t in terms if t != Top()}
    if len(used) == 0:
        return Top()
    elif len(used) == 1:
        return used.pop()
    else:
        return Conjunction(*used)

def disjunct(*terms):
    used = {t for t in terms if t != Bottom()}
    if len(used) == 0:
        return Bottom()
    elif len(used) == 1:
        return used.pop()
    else:
        return Disjunction(*used)
<|endoftext|>


<commit_before>"""
juxta.py File

"""
<commit_msg>add main code<commit_after>"""
juxta.py

"""

import os
import shutil
import filecmp
import difflib

# Constants
HTML_STR = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

    <meta http-equiv="Content-Type"
          content="text/html; charset=ISO-8859-1" />
    <title></title>

        <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup>
        <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup>

{from} (From)	{to} (To)

        {rows}

  Legends

 Colors

 Added

Changed

Deleted

'''

ROW_STR = '
{from}	{to}
'

# helper functions
def list_diff(list1, list2):
    """return list1 items not in list2"""
    return [x for x in list1 if x not in set(list2)]

def read_file(file_path='', encoding='utf-8'):
    """read a file into a string. assumes utf-8 encoding."""
    if os.path.exists(file_path):
        fid = codecs.open(file_path, mode='r', encoding=encoding)
        source = fid.read()
        fid.close()
    else:
        source = ''
    return source

def write_file(file_path='', data=''):
    """write a file from a string."""
    fid = codecs.open(file_path, 'w')
    fid.write(data)
    fid.close()

def get_file_diffs(from_file_path, to_file_path):
    """ndiff file compare"""
    from_file = read_file(from_file_path).splitlines()
    to_file = read_file(to_file_path).splitlines()
    file_diff_html = difflib.HtmlDiff().make_file(from_file, to_file)
    return file_diff_html

class Juxta(object):
    """compare folder and files by juxtaposing from (left) and to (right) directories"""

    def __init__(self, from_path='', to_path='', output_path='', file_filter=None, ignores=None)
        """create Juxta Object"""

        # set inputs
        self.file_filter = file_filter if file_filter else '*.pyc'
        self.ignores = ignores if ignores else ['.DS_Store', '.localized']
        self.from_path = from_path
        self.to_path = to_path
        self.compare_name = '{}_compare_{}'.format(os.path.basename(from_path), os.path.basename(to_path))

        # check for comparison type
        if os.path.isdir(from_path) and os.path.isdir(to_path):
            self.compare_type = 'dir'
        elif os.path.isfile(from_path) and os.path.isfile(to_path):
            self.compare_type = 'file'
        else:
            self.compare_type = 'error'

        # set output based on type
        self.output_path = os.path.join(output_path, self.compare_name)
        if self.compare_type == 'file':
            self.output_path += '.html'

    def compare(self):
        """compare folders and/or files"""

        if self.compare_type == 'dir':
            # check and clear output directory
            if os.path.exists(self.output_path):
                shutil.rmtree(self.output_path)
            os.makedirs(self.output_path)
            # compare directory
            compare = self.get_dir_diffs(filecmp.dircmp(self.from_path, self.to_path, self.ignores))
            compare = sorted(cmpr, key=lambda x: x["left"] or x["right"])
            file_rows = [ROW_STR.format(**x) for x in compare]
            compare_html = HTML_STR.format(**{
                'from' : self.from_path,
                'to' : self.to_path,
                'rows' :'\n'.join(file_rows)
            })
            # write
            write_file(os.path.join(self.output_path, 'index.html'), )

            return 'COMPARED: {}'.format(self.output_path)

        elif self.compare_type == 'file':
            # compare file
            file_compare_html = self.get_file_diffs(self.from_path, self.to_path)
            # write
            if not os.path.exists(os.path.dirname(self.output_path)):
                os.makedirs(os.path.dirname(self.output_path))
            write_file(self.output_path, file_compare_html)

            return 'COMPARED: {}'.format(self.output_path)

        else:
            return 'ERROR: from and to patch are not compatible'

    def get_dir_diffs(self, dcmp):
        """recursive directory and file compare"""

        diffs = []

        # show same files
        for name in dcmp.same_files:
            if not fnmatch.fnmatch(name, self.file_filters):
                diffs.append({
                    'type'   : '',
                    'from'   : os.path.join(dcmp.left, name).replace(self.from_path, ''),
                    'to'     : os.path.join(dcmp.right, name).replace(self.to_path, ''),
                    'compare': ''
                })

        # compare different files
        for name in dcmp.diff_files:
            if not fnmatch.fnmatch(name, self.file_filters):
                # paths
                from_file_path = os.path.join(dcmp.left, name)
                to_file_path = os.path.join(dcmp.right, name)
                compare_file_path = os.path.join(dcmp.left.replace(self.from_path, self.output_path), name) + '.html'
                # compare
                file_compare_html = self.get_file_diffs(from_file_path, to_file_path)
                diffs.append({
                    'type'   : 'diff_chg',
                    'from'   : from_file_path.replace(self.from_path, ''),
                    'to'     : to_file_path.replace(self.to_path, ''),
                    'compare': compare_file_path
                })
                # write
                if not os.path.exists(os.path.dirname(compare_file_path)):
                    os.makedirs(os.path.dirname(compare_file_path))
                write_file(compare_file_path, file_compare_html)

        # compare common subdirectories
        for sub_dcmp in dcmp.subdirs.values():
            diffs.extend(self.get_dir_diffs(sub_dcmp, dcmp.left))

        # check for close file and subdirectory matches
        close_dirs = []
        close_files = []
        for left in dcmp.left_only:
            match = get_close_matches(left, dcmp.right_only, 1)
            if match:
                close_paths = (os.path.join(dcmp.left, left), os.path.join(dcmp.right, match[0]))
                if all([path.isdir(x) for x in close_paths]):
                    close_dirs.append(close_paths)
                else:
                    close_files.append(close_paths)

        # compare close subdirectory matches
        for left_dir, right_dir in close_dirs:
            diffs.extend(self.get_dir_diffs(dircmp(left_dir, right_dir, self.ignores), left_dir))

        # compare close file matches
        for from_file_path, to_file_path in close_files:
            # paths
            compare_file_path = os.path.join(output_path, from_file_path.replace(self.from_path,''), name) + '.html'
            # compare
            file_compare_html = self.get_file_diffs(left_file, right_file)
            diffs.append({
                'type'   : 'diff_chg',
                'from'   : from_file_path.replace(self.from_path, ''),
                'to'     : to_file_path.replace(self.to_path, ''),
                'compare': compare_file_path
            })
            # write
            write_file(compare_file_path, file_compare_html)

        # add no match files and directories to diffs
        for no_match in list_diff(dcmp.left_only, [path.basename(x[0]) for x in close_files+close_dirs]):
            if not fnmatch.fnmatch(no_match, self.file_filters):
                diffs.append({
                    'type'   : 'diff_sub',
                    'from'   : os.path.join(dcmp.left, no_match).replace(self.from_path, ''),
                    'to'     : '',
                    'compare': ''
                })
        for no_match in list_diff(dcmp.right_only, [path.basename(x[1]) for x in close_files+close_dirs]):
            if not fnmatch.fnmatch(no_match, self.file_filters):
                diffs.append({
                    'type'   : 'diff_add',
                    'from'   : '',
                    'to'     : os.path.join(dcmp.right, no_match).replace(self.to_path, ''),
                    'compare': ''
                })

        return diffs
<|endoftext|>