class Build(object):
    def __init__(self, config):
        self._cwd = os.getcwd()
        self._config = config

    def run(self):
        if os.path.exists(self._config['build_path']):
            try:
                rmtree(self._config['build_path'])
            except:
                raise RemoveFolderError('Could not remove existing build directory.')

        try:
            os.makedirs(self._config['build_path'])
        except:
            raise CreateFolderError('Could not create the project folder.')

        try:
            self._build_javascript()
            self._build_style()
            self._build_html()
            self._build_libraries()
            self._copy_assets()
        except:
            raise

    def _build_javascript(self):
        js_name = self._config['js_name'] + '.js'
        source = os.path.join(self._cwd, 'src', js_name)
        dest = os.path.join(self._config['build_path'], js_name)
        js_string = ''

        if not os.path.exists(source):
            source = os.path.join(self._cwd, 'src', 'application.js')
            if not os.path.exists(source):
                source = os.path.join(self._cwd, 'src', 'application.coffee')
                if not os.path.exists(source):
                    return

        if source.endswith('coffee'):
            as_coffee = True
        else:
            as_coffee = False

        try:
            f = open(source)
            self._included_files = []

            js_string = self._gather_javascript(f, as_coffee)
        except FileNotFoundError:
            raise
        except ParseError:
            raise
        except:
            raise FileNotFoundError('The specified file does not exist: ', source)
        finally:
            f.close()

        try:
            f = open(dest, 'w+')
        except:
            raise FileNotWritableError('Could not write the javascript file.')

        if self._config['minify_js']:
            js_string = minify(js_string, mangle=True, mangle_toplevel=False)

        f.write(js_string)
        f.close()

    def _concat_javascript(self, source, as_coffee=False):
        f = None
        js_string = ''

        try:
            f = open(source)
        except:
            raise FileNotFoundError('The specified file does not exist: ', source)

        self._included_files = []
        try:
            js_string = self._gather_javascript(f)
        except FileNotFoundError:
            raise
        finally:
            f.close()

        return js_string

    def _gather_javascript(self, f, as_coffee=False):
        lines = []
        include_string = ''
        index = 0
        js_string = ''

        for line in f:
            line = line.replace('##BUILDVERSION##', self._config['version'])

            if as_coffee:
                match = re.match('#= require ([a-zA-Z\/-_]+)', line)
            else:
                match = re.match('\/\/= require ([a-zA-Z\/-_]+)', line)

            if match:
                include_f = None
                include_path = match.group(1)
                files_dir = os.path.join(self._cwd, 'src', 'javascript')
                if not os.path.exists(files_dir):
                    files_dir = os.path.join(self._cwd, 'src', 'coffeescript')
                    if not os.path.exists(files_dir):
                        raise FileNotFoundError('There is not javascript or coffeescript folder present in the src director.')

                if sys.platform.startswith('win32'):
                    include_path = include_path.replace('/', '\\')
                include_path = os.path.join(files_dir, include_path)

                if os.path.exists(include_path + '.js'):
                    include_path = include_path + '.js'
                    include_as_coffee = False
                elif os.path.exists(include_path + '.coffee'):
                    include_path = include_path + '.coffee'
                    include_as_coffee = True
                else:
                    raise FileNotFoundError('The specified file does not exist (as either .js or .coffee): ', include_path)

                if include_path not in self._included_files:
                    self._included_files.append(include_path)

                    include_f = open(include_path)

                    try:
                        include_string = include_string + self._gather_javascript(include_f, include_as_coffee)
                        index += 1
                    except FileNotFoundError:
                        raise
                    finally:
                        include_f.close()
            else:
                lines.append(line)

        js_string = ''.join(lines)
        if as_coffee:
            try:
                js_string = coffeescript.compile(js_string)
            except ProgramError as e:
                filename = f.name.replace(self._cwd, '')[1:]
                msg = ''.join(map(str, e.args)).replace('stdin', filename)

                raise ParseError('Error while parsing CoffeeScript\n' + msg)

        return include_string + js_string

    def _build_html(self):
        source = os.path.join(self._cwd, 'src', 'index.html')
        dest = os.path.join(self._config['build_path'], 'index.html')

        if not os.path.exists(source):
            return

        try:
            copy2(source, dest)
        except:
            raise FileNotWritableError('Could not write the html file.')

    def _build_style(self):
        source = os.path.join(self._cwd, 'src', 'style')
        destination = os.path.join(self._config['build_path'], 'style')

        if not os.path.exists(source):
            return

        temp_style_dir = tempfile.mkdtemp()

        try:
            copytree(source, os.path.join(temp_style_dir, 'style'))
        except:
            raise CreateFolderError('Could not copy your style folder to a temporary location.')

        self._work_css_files(os.path.join(temp_style_dir, 'style'))

        try:
            copytree(os.path.join(temp_style_dir, 'style'), destination)
        except:
            CreateFolderError('Could not copy the style folder from the temporary location.')

        try:
            rmtree(temp_style_dir)
        except:
            RemoveFolderError('Could not remove your temporary style folder.')

    def _work_css_files(self, folder):
        for root, dirs, files in os.walk(folder):
            for f in files:
                if f.endswith('.scss'):

                    namespace = scss.namespace.Namespace()

                    @namespace.declare_alias('dashboard-region')
                    def dashboard():
                        return scss.types.Function(u'control rectangle', u'dashboard-region', quotes=None)

                    @namespace.declare_alias('dashboard-region')
                    def dashboard(val):
                        return scss.types.Function(val.render(), u'dashboard-region', quotes=None)

                    compiler = scss.compiler.Compiler(namespace=namespace)

                    scss_filename = os.path.join(root, f)
                    css_filename = os.path.join(root, f[:-4] + 'css')

                    try:
                        css_string = compiler.compile(scss_filename)
                    except (scss.errors.SassEvaluationError, scss.errors.SassSyntaxError, scss.errors.SassParseError) as e:
                        raise ScssError(traceback.format_exc(0))
                    except scss.errors.SassError as e:
                        elist = traceback.format_exception_only(type(e), e)
                        raise ScssError(''.join(elist))
                    except:
                        raise FileNotFoundError('Could not compile your scss style file.')

                    try:
                        css_file = open(css_filename, 'w+')
                    except:
                        raise FileNotWritableError('Could not write the new css file:\n' + css_filename)

                    if self._config['minify_css']:
                        css_string = cssmin(css_string)

                    css_file.write(css_string)
                    css_file.close()

                    try:
                        os.remove(scss_filename)
                    except:
                        raise FileNotWritableError('Could not remove already converted scss file:\n' + scss_filename)
                elif f.endswith('.css'):
                    if self._config['minify_css']:
                        css_filename = os.path.join(root, f)

                        try:
                            css_file = open(f, 'r')
                        except:
                            raise FileNotReadableError('Could not read your css file:\n' + css_filename)

                        css_string = f.read()
                        css_file.close()

                        try:
                            os.remove(css_file)
                        except:
                            raise FileNotWritableError('Could not remove your non-minified css file:\n' + css_filename)

                        try:
                            css_file = open(css_filename, 'w+')
                        except:
                            FileNotWritableError('Could not write to the new minified css file:\n' + css_filename)

                        css_string = cssmin(css_string)
                        css_file.write(css_string)
                        css_file.close()

    def _build_libraries(self):
        source = os.path.join(self._cwd, 'src', 'lib')
        dest = os.path.join(self._config['build_path'], 'lib')

        if not os.path.exists(source):
            return

        try:
            copytree(source, dest)
        except:
            raise FileNotWritableError('Could not copy all the libraries.')

    def _copy_assets(self):
        source = os.path.join(self._cwd, 'assets')
        dest = os.path.join(self._config['build_path'], 'assets')

        if not os.path.exists(source):
            return

        try:
            copytree(source, dest)
        except:
            raise FileNotWritableError('Could not copy all the asset files.') def clean():
    if not os.path.exists(os.path.join(os.getcwd(), 'build')):
        return

    try:
        rmtree(os.path.join(os.getcwd(), 'build'))
    except:
        raise RemoveFolderError('Could not remove the build folder.') class Deploy(object):
    def __init__(self, config):
        self._cwd = os.getcwd()
        self._config = config

        if 'deployment_path' not in self._config:
            raise MissingKeyError('Could not find deployment path in config file.')
        else:
            self._deployment_path = self._config['deployment_path']

    def run(self, testname):
        if self._config['test']:
            if testname is None:
                print('No tests to build.')
                return

            dest = os.path.join(self._deployment_path, self._config['name'] + '_' + testname)
            source = os.path.join(self._cwd, 'build', self._config['name'] + '_' + testname)
        elif self._config['build']:
            dest = os.path.join(self._deployment_path, self._config['name'])
            source = os.path.join(self._cwd, 'build', self._config['name'])
        else:
            raise MissingKeyError('It seems you are trying to deploy a project but neither build nor test were specified. I am sorry but I do not know what to do now.')

        self._deploy(source, dest)

    def _deploy(self, source, dest):
        if os.path.exists(dest):
            try:
                rmtree(dest)
            except:
                raise RemoveFolderError('Could not remove the existing deployment path.')

        try:
            copytree(source, dest)
        except:
            raise FolderNotWritableError('Could not copy the build directory to the deployment path.') class Zip(object):
    def __init__(self, config):
        self._cwd = os.getcwd()
        self._config = config

        if 'zip_path' not in self._config:
            self._zip_path = None
        else:
            self._zip_path = os.path.join(self._config['zip_path'])

        if 'zip_name' not in self._config:
            self._zip_name = self._config['name'] + '-' + self._config['version'] + '.zip'
        else:
            self._zip_name = self._config['zip_name']

    def run(self, testname):
        if self._config['test']:
            if testname is None:
                print('No tests to build.')
                return

            name = self._config['name'] + '_' + testname
            source = os.path.join(self._cwd, 'build', self._config['name'] + '_' + testname)
            zip_name = testname + '_' + self._zip_name
        elif self._config['build']:
            name = self._config['name']
            source = self._config['build_path']
            zip_name = self._zip_name
        else:
            raise MissingKeyError('It seems you are trying to zip a project but neither build nor test were specified. I am sorry but I do not know what to do now.')

        try:
            self._zip(name, source, os.path.join(self._cwd, 'build', zip_name))

            if self._zip_path is not None:
                self._zip(name, source, os.path.join(self._zip_path, zip_name))
        except:
            raise

    def _zip(self, name, source, dest):
        try:
            self._cleanup(dest)
        except:
            raise

        try:
            z = zipfile.ZipFile(dest, 'a', zipfile.ZIP_DEFLATED)
        except RuntimeError as e:
            z = zipfile.ZipFile(dest, 'a')

        for root, dirs, files in os.walk(source):
            for f in files:
                tmpfilename = os.path.join(root, f).split(source)[1][1:]
                zipfilename = os.path.join(name, tmpfilename)
                try:
                    z.write(os.path.join(root, f), zipfilename)
                except:
                    raise FileNotWritableError('Could not write to the zip file.')

        z.close()

    def _cleanup(self, path):

        if os.path.exists(path):
            try:
                os.remove(path)
            except:
                raise RemoveFileError('Could not remove the zip file in build directory.') class Test(object):
    def __init__(self, config):
        self._cwd = os.getcwd()
        self._config = config

    def run(self, testname):
        if testname is None:
            print('No tests to build.')
            return

        build_path = os.path.join(self._cwd, 'build', self._config['name'] + '_' + testname)
        js_source_path = os.path.join(self._cwd, 'test', 'tests', 'test_' + testname + '.js')

        try:
            self._clean_previous_tests(build_path)
            self._build_javascript(build_path, js_source_path)
            self._build_libraries(build_path)
            self._build_html(build_path)
            self._copy_assets(build_path)
        except:
            raise

    def _clean_previous_tests(self, build_path):
        if os.path.exists(build_path):
            try:
                rmtree(build_path)
            except:
                raise RemoveFolderError('Could not delete the test folder: ' + build_path)

        try:
            os.makedirs(build_path)
        except:
            raise CreateFolderError('Could not create the test folder: ' + build_path)

    def _build_javascript(self, build_path, js_source_path):
        self._js_string_lines = []

        dest = os.path.join(build_path, 'test.js')
        js_string_lines = self._concat_javascript(js_source_path)

        if os.path.exists(dest):
            try:
                os.remove(dest)
            except:
                raise RemoveFileError('Could not delete the existing javascript test file.')


        try:
            f = open(dest, 'w+')
        except:
            raise FileNotWritableError('Could not write the javascript test file.')

        js_string = ''.join(js_string_lines)
        f.write(js_string)
        f.close()

    def _concat_javascript(self, js_source_path):
        f = None
        lines = []

        try:
            f = open(js_source_path)
        except:
            raise FileNotFoundError('The specified file does not exist: ', js_source_path)

        self._included_js_files = []
        try:
            lines = self._gather_javascript_lines(f)
        except FileNotFoundError:
            raise
        finally:
            f.close()

        return lines

    def _gather_javascript_lines(self, f):
        lines = []

        for line in f:
            match = re.match('\/\/= require ([a-zA-Z\/-_]+)', line)
            if match:
                sub_f = None

                path = match.group(1)
                if sys.platform.startswith('win32'):
                    path = path.replace('/', '\\')

                sub_path = os.path.join(self._cwd, 'test', 'javascript', path + '.js')
                if not os.path.exists(sub_path):
                    sub_path = os.path.join(self._cwd, 'src', 'javascript', path + '.js')

                if sub_path not in self._included_js_files:
                    self._included_js_files.append(sub_path)

                    try:
                        sub_f = open(sub_path)
                    except:
                        raise FileNotFoundError('The specified file does not exist: ', sub_path)

                    try:
                        lines = lines + self._gather_javascript_lines(sub_f)
                    except FileNotFoundError:
                        raise
                    finally:
                        sub_f.close()
            else:
                lines.append(line)

        lines.append('\n')
        return lines

    def _build_html(self, build_path):
        source = os.path.join(self._cwd, 'test', 'index.html')
        dest = os.path.join(build_path, 'index.html')
        if not os.path.exists(source):
            return

        if os.path.exists(dest):
            try:
                os.remove(dest)
            except:
                raise RemoveFileError('Could not remove the existing html test file.')

        try:
            copy2(source, dest)
        except:
            raise FileNotWritableError('Could not write the html file.')

    def _build_libraries(self, build_path):
        source = os.path.join(self._cwd, 'test', 'lib')
        dest = os.path.join(build_path, 'lib')

        if not os.path.exists(source):
            return

        if os.path.exists(dest):
            try:
                rmtree(dest)
            except:
                raise RemoveFolderError('Could not remove the existing libraries folder.')

        try:
            copytree(source, dest)
        except:
            raise FileNotWritableError('Could not copy all the libraries.')

    def _copy_assets(self, build_path):
        source = os.path.join(self._cwd, 'assets')
        dest = os.path.join(build_path, 'assets')

        if not os.path.exists(source):
            return

        if os.path.exists(dest):
            try:
                rmtree(dest)
            except:
                raise RemoveFolderError('Could not remove the existing assets folder.')

        try:
            copytree(source, dest)
        except:
            raise FileNotWritableError('Could not copy all asset files') class New(object):
    def __init__(self, projectName, skeleton):
        self._projectName = projectName
        self._root = get_path()
        self._cwd = os.getcwd()

        self._skeleton_parent_folder = os.path.join(os.path.expanduser('~'), '.grace', 'skeletons', 'custom')
        self._skeleton_path = os.path.join(self._skeleton_parent_folder, hashlib.md5(skeleton.encode(sys.getfilesystemencoding())).hexdigest())
        self._skeleton_url = skeleton

        if skeleton == 'default':
            self._skeleton_url = 'https://github.com/mdiener/grace-skeleton/archive/default.zip'
            self._skeleton_parent_folder = os.path.join(os.path.expanduser('~'), '.grace', 'skeletons', 'grace')
            self._skeleton_path = os.path.join(self._skeleton_parent_folder, 'default')

        self._download_skeleton()

        self._projectPath = os.path.join(self._cwd, self._projectName)

        self._copy_structure()
        self._replace_strings()

    def _download_skeleton(self):
        tmp_path = tempfile.mkdtemp()
        zip_path = os.path.join(tmp_path, 'skeleton.zip')

        try:
            r = requests.get(self._skeleton_url, stream=True)
        except requests.exceptions.ConnectionError as e:
            rmtree(tmp_path)
            if not os.path.exists(self._skeleton_path):
                raise FolderNotFoundError('Could not download the skeleton and no downloaded skeleton was found.')
            return

        with open(zip_path, 'wb') as f:
            for chunk in r.iter_content(chunk_size=1024):
                if chunk: # filter out keep-alive new chunks
                    f.write(chunk)
                    f.flush()

        try:
            z = zipfile.ZipFile(zip_path, 'r')
            z.extractall(tmp_path)
        except:
            z.close()
            rmtree(tmp_path)
            raise GeneralError('Could not unzip the downloaded file. Something went wrong, please try again.')

        if not os.path.exists(self._skeleton_parent_folder):
            try:
                os.makedirs(self._skeleton_parent_folder)
            except:
                z.close()
                rmtree(tmp_path)
                raise FolderNotWritableError('Could not create the skeleton parent folder.')

        if os.path.exists(self._skeleton_path):
            try:
                rmtree(self._skeleton_path)
            except:
                z.close()
                rmtree(tmp_path)
                raise FolderNotWritableError('Could not remove the saved skeleton.')

        try:
            copytree(os.path.join(tmp_path, z.namelist()[0]), self._skeleton_path)
        except:
            z.close()
            rmtree(tmp_path)
            raise FolderNotFoundError('Could not find the folder to save the skeleton.')

        z.close()
        rmtree(tmp_path)

    def _copy_structure(self):
        if os.path.exists(os.path.join(self._cwd, self._projectName)):
            raise FolderAlreadyExistsError('There is already a folder with the projectname present!')

        if not os.path.exists(self._skeleton_path):
            raise FolderNotFoundError('Could not find the skeleton: ', self._skeleton_path)

        try:
            copytree(self._skeleton_path, os.path.join(self._cwd, self._projectName))
        except:
            raise CreateFolderError('Could not create the folders for the new project.')

    def _replace_strings(self):
        file_list = []
        for path, dirs, files in os.walk(self._projectPath):
            for f in files:
                file_list.append({
                    'file': f,
                    'path': path
                })

        for entry in file_list:
            if re.search('_X.', entry['file']):
                try:
                    self._replace(entry)
                except:
                    raise

    def _replace(self, fileObject):
        f = fileObject['file']
        p = fileObject['path']

        outfilename = f.replace('_X', '')
        with open(os.path.join(p, outfilename), 'w+') as out:
            infile = open(os.path.join(p, f))
            for line in infile:
                newline = line.replace('##PROJECTNAME##', self._projectName)
                newline = newline.replace('##PROJECTNAME_TOLOWER##', self._projectName.lower())

                out.write(newline)

            infile.close()

        try:
            os.remove(os.path.join(p, f))
        except:
            raise FileNotWritableError('Could not delete the initial replace file.') class Doc(object):
    def __init__(self, config):
        self._cwd = os.getcwd()
        self._config = config

        if 'doc_path' not in self._config:
            self._doc_path = None
        else:
            self._doc_path = os.path.join(self._config['doc_path'], self._config['name'])

        self._lib_path = os.path.join(self._cwd, 'src', 'lib')

    def run(self, with_libs=False):
        source = os.path.join(self._cwd, 'src', 'javascript')
        dest = os.path.join(self._cwd, 'build', 'JSDocs')

        if not os.path.exists(self._config['build_path']):
            try:
                os.makedirs(self._config['build_path'])
            except:
                raise CreateFolderError('Could not create the project folder.')

        if os.path.exists(dest):
            try:
                rmtree(dest)
            except:
                raise RemoveFolderError('Could not remove the JSDoc folder.')

        try:
            os.makedirs(dest)
        except:
            raise CreateFolderError('Could not create the doc folder in your build directory.')

        if with_libs:
            jsdoc = pyjsdoc.CodeBaseDoc([source, self._lib_path])
        else:
            jsdoc = pyjsdoc.CodeBaseDoc([source])

        try:
            jsdoc.save_docs(output_dir=dest)
        except:
            raise FileNotWritableError('Could not write the docs to the build directory.')

        if self._doc_path is not None:
            if os.path.exists(self._doc_path):
                try:
                    rmtree(self._doc_path)
                except:
                    raise RemoveFolderError('Could not remove the current JSDoc folder.')

            if not os.path.exists(self._doc_path):
                try:
                    os.makedirs(self._doc_path)
                except:
                    raise CreateFolderError('Could not create the doc folder at "' + self._doc_path + '".')

            try:
                jsdoc.save_docs(output_dir=self._doc_path)
            except:
                raise FileNotWritableError('Could not write the docs to target directory (' + self._doc_path + ').') class Update(object):
    def __init__(self, config, test=False, target=None):
        self._cwd = os.getcwd()
        self._root = get_path()
        self._config = config
        self._test = test
        self._target = target

        try:
            self._skeleton_path = resource_filename(__name__, os.path.join('skeleton', 'default'))
        except NotImplementedError:
            self._skeleton_path = os.path.join(sys.prefix, 'skeleton', 'default')

        try:
            self._assetPath = os.path.join(resource_filename(__name__, os.path.join('assets', 'manage.py')))
        except NotImplementedError:
            self._assetPath = os.path.join(sys.prefix, 'assets', 'manage.py')

        self._projectName = self._config['name']
        if 'deployment_path' in self._config:
            self._deployment_path = self._config['deployment_path']
        else:
            self._deployment_path = ''
        if 'zip_path' in self._config:
            self._zip_path = self._config['zip_path']
        else:
            self._zip_path = ''
        if 'doc_path' in self._config:
            self._doc_path = self._config['doc_path']
        else:
            self._doc_path = ''

    def run(self):
        if self._target is None:
            print('Do you really want to update all files? Changes to any files you might have done will be lost in the process!')
            ack = input('Continue: yes/[no] ')
            if ack is not 'yes':
                print('Canceling update.')
                sys.exit()

            print('Updating everything ...')

            self._update_all()

        if self._target is 'libs':
            print('Updating libs directory ...')
            self._update_libs()

        if self._target is 'html':
            print('Updating index.html file ...')
            self._update_html()

        if self._target is 'config':
            print('Update project.cfg file ...')
            self._update_config()

        if self._target is 'javascript':
            print('Update JavaScript files ...')
            self._update_javascript()

        if self._target is 'css':
            print('Update css files ...')
            self._update_css()

    def _update_all(self):
        if not self._test:
            try:
                self.update_css()
                self.update_config()
            except:
                raise

        try:
            self.update_libs()
            self.update_html()
            self.update_javascript()
        except:
            raise

    def _update_config(self):
        new_config_path = os.path.join(self._skeleton_path, 'project_X.cfg')
        current_config_path = os.path.join(self._cwd, 'project.cfg')

        if not os.path.exists(new_config_path):
            raise FileNotFoundError('Could not find the new project config file at: ' + new_config_path)

        if os.path.exists(current_config_path):
            try:
                os.remove(current_config_path)
            except:
                raise RemoveFileError('Could not remove the old project.cfg file at: ' + current_config_path)

        self._replace_lines_and_copy(new_config_path, current_config_path)

    def _update_libs(self):
        if self._test:
            new_libs_dir = os.path.join(self._skeleton_path, 'test', 'lib')
            current_libs_dir = os.path.join(self._cwd, 'test', 'lib')
        else:
            new_libs_dir = os.path.join(self._skeleton_path, 'src', 'lib')
            current_libs_dir = os.path.join(self._cwd, 'src', 'lib')

        if not os.path.exists(new_libs_dir):
            return

        try:
            self._update_files(new_libs_dir, current_libs_dir)
        except:
            raise

    def _update_html(self):
        if self._test:
            new_html_path = os.path.join(self._skeleton_path, 'test', 'index_X.html')
            current_html_path = os.path.join(self._cwd, 'test', 'index.html')
        else:
            new_html_path = os.path.join(self._skeleton_path, 'src', 'index_X.html')
            current_html_path = os.path.join(self._cwd, 'src', 'index.html')

        if not os.path.exists(new_html_path):
            raise FileNotFoundError('Could not find the new html file at: ' + new_html_path)

        if os.path.exists(current_html_path):
            try:
                os.remove(current_html_path)
            except:
                raise RemoveFileError('Could not remove the old index.html file at: ' + current_html_path)

        self._replace_lines_and_copy(new_html_path, current_html_path)

    def _update_javascript(self):
        if self._test:
            new_javascript_dir = os.path.join(self._skeleton_path, 'test', 'javascript')
            current_javascript_dir = os.path.join(self._cwd, 'test', 'javascript')
        else:
            new_javascript_dir = os.path.join(self._skeleton_path, 'src', 'javascript')
            current_javascript_dir = os.path.join(self._cwd, 'src', 'javascript')
            new_js_app_file = os.path.join(self._skeleton_path, 'src', 'application_X.js')
            current_js_app_file = os.path.join(self._cwd, 'src', 'application.js')

        if not self._test:
            try:
                os.remove(current_js_app_file)
            except:
                raise FileNotWritableError('Could not remove the old js application file.')
            self._replace_lines_and_copy(new_js_app_file, current_js_app_file)

        if not os.path.exists(new_javascript_dir):
            return

        try:
            self._update_files(new_javascript_dir, current_javascript_dir)
        except:
            raise

    def _update_css(self):
        new_css_dir = os.path.join(self._skeleton_path, 'src', 'style')
        current_css_dir = os.path.join(self._cwd, 'src', 'style')

        if not os.path.exists(new_css_dir):
            return

        try:
            self._update_files(new_css_dir, current_css_dir)
        except:
            raise

    def _update_files(self, in_dir, out_dir):
        for path, dirs, files in os.walk(in_dir):
            for f in files:
                file_in_path = os.path.join(path, f)
                directory = path.split(in_dir)[1][1:]
                file_out_path = os.path.join(out_dir, directory, f.replace('_X', ''))
                dir_out_path = os.path.join(out_dir, directory)

                if not os.path.exists(dir_out_path):
                    try:
                        os.makedirs(dir_out_path)
                    except OSError as exc:
                        if exc.errno == errno.EEXIST and os.path.isdir(dir_out_path):
                            pass
                        else:
                            raise
                else:
                    try:
                        os.remove(file_out_path)
                    except:
                        if os.path.exists(file_out_path):
                            raise FileNotWritableError('Could not remove the old file at: ' + file_out_path)

                self._replace_lines_and_copy(file_in_path, file_out_path)

    def _replace_lines_and_copy(self, file_in_path, file_out_path):
        with open(file_out_path, 'w+') as file_out:
            file_in = open(file_in_path)

            for line in file_in:
                try:
                    newline = line.replace('##DEPLOYMENTPATH##', self._deployment_path)
                    newline = newline.replace('##ZIPPATH##', self._zip_path)
                    newline = newline.replace('##PROJECTNAME##', self._projectName)
                    newline = newline.replace('##PROJECTNAME_TOLOWER##', self._projectName.lower())
                    newline = newline.replace('##DOCPATH##', self._doc_path)
                except UnicodeDecodeError as exc:
                    newline = line

                file_out.write(newline)

        file_in.close() class Upload(object):
    def __init__(self, config):
        self._cwd = os.getcwd()
        self._root = get_path()
        self._config = config
        self._verify_ssl = False

        self._username = None
        self._password = None

        self._check_config()

    def _check_config(self):
        if 'urls' not in self._config:
            raise MissingKeyError('Could not find url settings in either global or local configuration file.')

        if 'upload' not in self._config['urls']:
            raise MissingKeyError('Could not find an upload url in either the global or local configuration file.')
        else:
            self._upload_url = self._config['urls']['upload']

        if 'login' not in self._config['urls']:
            self._login_url = self._upload_url
        else:
            self._login_url = self._config['urls']['login']

        if 'credentials' in self._config:
            if 'username' in self._config['credentials']:
                self._username = self._config['credentials']['username']
            if 'password' in self._config['credentials']:
                self._password = self._config['credentials']['password']

        if 'zip_name' not in self._config:
            self._zip_name = self._config['name'] + '-' + self._config['version'] + '.zip'
        else:
            self._zip_name = self._config['zip_name']

        self._zip_path = os.path.join(self._cwd, 'build', self._zip_name)

    def run(self):
        self._login()

    def _get_login_information(self):
        data = {}

        if self._username is None:
            self._username = input('Please provide the username for your upload server (or leave blank if none is required): ')

        if self._password is None:
            self._password = getpass.getpass('Please provide the password for your upload server (or leave blank if none is required): ')

        if self._username != '':
            data['username'] = self._username
        if self._password != '':
            data['password'] = self._password

        return data

    def _login(self):
        data = self._get_login_information()

        r = requests.post(self._login_url,
            data=write_json(data),
            headers={'Content-Type': 'application/json'},
            verify=self._verify_ssl
        )

        self._cookies = r.cookies

        self._login_response(r)

    def _login_response(self, r):
        if r.status_code != 200:
            raise WrongLoginCredentials('Could not log in with the given credentials.')

        self._upload()

    def _upload(self):
        if not os.path.exists(self._zip_path):
            raise FileNotFoundError('Could not find the zip file. Please check if "' + self._zip_path + '" exists.')

        try:
            zip_file = open(self._zip_path, 'rb')
        except:
            raise GeneralError('Something went wrong while opening the zip file. Please try again.')

        r = requests.post(self._upload_url,
            files={'file': zip_file},
            cookies=self._cookies,
            verify=self._verify_ssl
        )

        self._upload_response(r)

    def _upload_response(self, r):
        if r.status_code != 201:
            print(r.text)
            raise RemoteServerError('Could not upload the file to the server. Please try again.') class Lint(object):
    def __init__(self, config):
        self._cwd = os.getcwd()
        self._config = config
        self.lint_valid = False

        self._jshint_node_script = r"""
var jshint = require("%s").JSHINT;
var readFileSync = require("fs").readFileSync;
var warnings = null;
var i = 0;
var lint = null;
var src = null;
var data = null;
var options = null;

//process.stdout.write("Analyzing file " + process.argv[2] + "\n");
src = readFileSync(process.argv[2], "utf8");
options = '%s';
options = JSON.parse(options);

jshint(src, options);

data = jshint.data();

if (data.errors && data.errors.length > 0) {
    process.stdout.write('\nErrors\n------\n')
    for (index in data.errors) {
        error = data.errors[index];
        try {
            process.stdout.write('File ' + process.argv[2] + '\n');
            process.stdout.write('Error ' + error.code + ' on line ' + error.line + ' character ' + error.character + ': ' + error.reason + '\n');
            process.stdout.write('\t' + error.evidence.trim() + '\n');
        } catch (err) {
            process.stdout.write()
        }
    };

    process.stdout.write('\n' + data.errors.length + ' Error(s) found.\n');
    process.stdout.write(' \n');
} else {
    //process.stdout.write('No errors found.\n');
    //process.stdout.write(' \n');
}

/*
if (data.unused && data.unused.length > 0) {
    process.stdout.write('Unused\n------\n')
    for (index in data.unused) {
        unused = data.unused[index]
        process.stdout.write('Unused \'' + unused.name + '\' on line ' + unused.line + ' character ' + unused.character + '\n');
    };
} else {
    //process.stdout.write('No unused found.')
}
*/

if (data.errors) {
    process.exit(1);
} else {
    process.exit(0);
}
"""

        self._jslint_node_script = r"""
var jslint = require("%s").jslint;
var readFileSync = require("fs").readFileSync;
var warnings = null;
var i = 0;
var lint = null;
var src = null;
var options = null;

process.stdout.write("Analyzing file " + process.argv[2] + "\n");
src = readFileSync(process.argv[2], "utf8");
options = '%s';
options = JSON.parse(options);

lint = jslint(src, options, ['dizmo', 'viewer', 'bundle', 'window', 'Class', 'jQuery', 'console']);

for (i = 0; i < lint.warnings.length; i++ ) {
    error = lint.warnings[i];
    if (error !== null) {
        process.stdout.write(' \n')
        process.stdout.write('Lint Warning at line ' + error.line + ' column ' + error.column + ': ' + error.message + '\n');
        process.stdout.write('\t' + lint.lines[error.line].trim() + '\n');
    }
}

if (lint.warnings.length > 0) {
    process.stdout.write("\n" + lint.warnings.length + " Error(s) found.\n");
    process.exit(1);
} else {
    process.stdout.write("\nNo warnings found.\n");
    process.exit(0);
}
"""

        if self._config['linter'] == 'jslint':
            self._options = {
                'jslint': os.path.join(os.path.expanduser('~'), '.grace', 'lint', 'jslint.js'),
                'jsoptions': {
                    'maxerr': 100,
                    'browser': True,
                    'fudge': True,
                    'for': True
                },
                'node': 'node'
            }
        else:
            self._options = {
                'jshint': os.path.join(os.path.expanduser('~'), '.grace', 'lint', 'jshint.js'),
                'jsoptions': {
                    'maxerr': 200,
                    'browser': True,
                    'undef': True,
                    'predef': {
                        'jQuery': True,
                        'console': True,
                        'Class': True
                    }
                },
                'node': 'node'
            }

        if 'ignore' in self._config['lintoptions']:
            self._options['ignore'] = copy.deepcopy(self._config['lintoptions']['ignore'])
            self._config['lintoptions'].pop('ignore')

        tmp = update(copy.deepcopy(self._options['jsoptions']), self._config['lintoptions'])
        self._options['jsoptions'] = write_json(tmp)

    def _check_executable(self, program):
        def is_exe(fpath):
            return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

        fpath, fname = os.path.split(program)
        if fpath:
            if is_exe(program):
                return True
        else:
            for path in os.environ["PATH"].split(os.pathsep):
                path = path.strip('"')
                exe_file = os.path.join(path, program)
                if is_exe(exe_file):
                    return True

        return False

    def run(self):
        if self._check_executable('nodejs'):
            self._options['node'] = 'nodejs'
        elif self._check_executable('node'):
            self._options['node'] = 'node'
        else:
            raise NoExectuableError('Could not find a node js executable on the system.')

        if not os.path.exists(os.path.join(self._cwd, 'src')):
            raise FolderNotFoundError('Could not find the source folder for the project.')

        application = os.path.join('src', 'application.js')

        self.lint_valid = True

        if os.path.exists(application):
            valid = self._lint_file(application)
            if not valid:
                self.lint_valid = False

        if os.path.exists(os.path.join(self._cwd, 'src', 'javascript')):
            for dirname, subdirs, files in os.walk(os.path.join('src', 'javascript')):
                for filename in files:
                    lint = True
                    if os.path.splitext(filename)[1] == '.js':
                        if 'ignore' in self._options:
                            for ignorename in self._options['ignore']:
                                if os.path.join(dirname, filename) == ignorename:
                                    lint = False
                    else:
                        lint = False

                    if lint:
                        valid = self._lint_file(os.path.join(dirname, filename))
                        if not valid:
                            self.lint_valid = False

    def _lint_file(self, jsfile):
        if 'jslint' in self._options:
            lint = self._get_jslint()
        else:
            lint = self._get_jshint()

        command = [self._options['node'], lint.name, jsfile]
        err = subprocess.call(command)

        lint.close()

        if err == 0:
            return True
        else:
            return False

    def _get_jshint(self):
        jshint_path = self._options['jshint']

        if self._day_old(jshint_path):
            try:
                response = requests.get('https://raw.githubusercontent.com/jshint/jshint/master/dist/jshint.js', stream=True)

                if not response.ok:
                    raise requests.exceptions.ConnectionError

                if not os.path.exists(os.path.dirname(jshint_path)):
                    os.makedirs(os.path.dirname(jshint_path))

                with open(jshint_path, 'wb') as f:
                    for chunk in response.iter_content(chunk_size=1024):
                        if chunk: # filter out keep-alive new chunks
                            f.write(chunk)
                            f.flush()

            except requests.exceptions.ConnectionError as e:
                pass

        jshint = NamedTemporaryFile('w+')
        jshint.write(self._jshint_node_script % (jshint_path, self._options['jsoptions']))
        jshint.file.flush()
        return jshint

    def _get_jslint(self):
        jslint_path = self._options['jslint']

        if self._day_old(jslint_path):
            try:
                response = requests.get('https://raw.github.com/douglascrockford/JSLint/master/jslint.js', stream=True)

                if not response.ok:
                    raise requests.exceptions.ConnectionError

                if not os.path.exists(os.path.dirname(jslint_path)):
                    os.makedirs(os.path.dirname(jslint_path))

                with open(jslint_path, 'wb') as f:
                    for chunk in response.iter_content(chunk_size=1024):
                        if chunk: # filter out keep-alive new chunks
                            f.write(chunk)
                            f.flush()
                    f.write('\n\nexports.jslint = jslint')

            except requests.exceptions.ConnectionError as e:
                pass

        jslint = NamedTemporaryFile('w+')
        jslint.write(self._jslint_node_script % (jslint_path, self._options['jsoptions']))
        jslint.file.flush()
        return jslint

    def _day_old(self, path):
        try:
            last_modified = os.path.getmtime(path)
        except:
            last_modified = 0

        day_ago = time.time() - (24 * 60 * 60)

        if last_modified < day_ago:
            return True
        else:
            return False def update(d, u):
    for k, v in u.items():
        if isinstance(v, collections.Mapping):
            r = update(d.get(k, {}), v)
            d[k] = r
        else:
            d[k] = u[k]
    return d class UnknownCommandError(Error):
    pass class NoExectuableError(Error):
    pass class FolderNotFoundError(Error):
    pass class SubProjectError(Error):
    pass
from builtins import input
from builtins import object
from .build import Build, clean
from .deploy import Deploy
from .zipit import Zip
from .testit import Test
from .create import New
from .doc import Doc
from .update import Update
from .upload import Upload
from .lint import Lint
from .utils import update
from .error import UnknownCommandError, NoExectuableError, FolderNotFoundError, SubProjectError
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from tempfile import mkdtemp
from shutil import rmtree
import os
import sys
import time
import git
import subprocess
import shlex
import zipfile
import tarfile
import requests
import json
import threading
    def exec_autodeploy(self):
        deploying_flag = os.path.join(os.path.expanduser('~'), '.grace', 'autodeploy', 'deploying')
        changes_flag = os.path.join(os.path.expanduser('~'), '.grace', 'autodeploy', 'changes_detected')

        if os.path.exists(deploying_flag):
            if not os.path.exists(changes_flag):
                f = open(changes_flag, 'a')
                try:
                    os.utime(changes_flag, None)
                finally:
                    f.close()

            return
        else:
            f = open(deploying_flag, 'a')
            try:
                os.utime(deploying_flag, None)
            finally:
                f.close()

        if self._config['autolint']:
            valid = self.exec_lint(True)
            if not valid:
                os.remove(deploying_flag)
                print('The JavaScript could not be linted and therefore no building/deploying will happen.')
                return

        self.exec_build()
        self.exec_deploy(None)

        os.remove(deploying_flag)

        if os.path.exists(changes_flag):
            os.remove(changes_flag)
            self.exec_autodeploy()

    def exec_build(self, silent=False):
        if self._module is not None:
            try:
                b = getattr(self._module.plugin, 'Build')(self._config)
            except AttributeError:
                b = Build(self._config)
        else:
            b = Build(self._config)

        b.run()

        if not silent:
            print('Successfully built the project.')

    def exec_deploy(self, testname, silent=False):
        if self._module is not None:
            try:
                d = getattr(self._module.plugin, 'Deploy')(self._config)
            except AttributeError:
                d = Deploy(self._config)
        else:
            d = Deploy(self._config)

        d.run(testname)

        if not silent:
            if testname is not None:
                print('Successfully deployed the test: ' + testname + '.')
            else:
                print('Successfully deployed the project.')

    def exec_zip(self, testname, silent=False):
        if self._module is not None:
            try:
                z = getattr(self._module.plugin, 'Zip')(self._config)
            except AttributeError:
                z = Zip(self._config)
        else:
            z = Zip(self._config)

        z.run(testname)

        if not silent:
            if testname is not None:
                print('Successfully zipped the test: ' + testname + '.')
            else:
                print('Successfully zipped the project.')

    def exec_test(self, testname, silent=False):
        if self._module is not None:
            try:
                t = getattr(self._module.plugin, 'Test')(self._config)
            except AttributeError:
                t = Test(self._config)
        else:
            t = Test(self._config)

        t.run(testname)

        if not silent:
            if testname is not None:
                print('Successfully built the test: ' + testname + '.')
            else:
                print('Successfully built the test.')

    def exec_jsdoc(self, silent=False):
        if self._module is not None:
            try:
                d = getattr(self._module.plugin, 'Doc')(self._config)
            except AttributeError:
                d = Doc(self._config)
        else:
            d = Doc(self._config)

        d.run()

        if not silent:
            print('Successfully built the JSDoc documentation.')

    def exec_upload(self, silent=False):
        if self._module is not None:
            try:
                u = getattr(self._module.plugin, 'Upload')(self._config)
            except AttributeError:

                u = Upload(self._config)