class Task(object):
    def __init__(self, task, config, module, test_cases):
        self._build = False
        self._jsdoc = False
        self._test = False
        self._deploy = False
        self._zip = False
        self._clean = False
        self._bad = False
        self._update = False
        self._update_test = False
        self._update_target = None
        self._upload = False
        self._autodeploy = False
        self._lint = False
        self._overwrite_args = []

        self._config = config
        self._module = module

        self._config['build'] = False
        self._config['test'] = False
        self._test_cases = test_cases

        if task == 'clean':
            self._clean = True
        else:
            if task == 'build':
                self._build = True
            if task == 'jsdoc':
                self._jsdoc = True
            if task == 'deploy':
                self._build = True
                self._deploy = True
            if task == 'zip':
                self._build = True
                self._zip = True
            if task == 'test':
                self._test = True
            if task == 'test:deploy':
                self._test = True
                self._deploy = True
            if task == 'test:zip':
                self._test = True
                self._zip = True
            if task == 'update':
                self._update = True
            if task == 'update:libs':
                self._update = True
                self._update_target = 'libs'
            if task == 'update:html':
                self._update = True
                self._update_target = 'html'
            if task == 'update:config':
                self._update = True
                self._update_target = 'config'
            if task == 'update:javascript':
                self._update = True
                self._update_target = 'javascript'
            if task == 'update:css':
                self._update = True
                self._update_target = 'css'
            if task == 'update:test':
                self._update = True
                self._update_test = True
            if task == 'update:test:libs':
                self._update = True
                self._update_test = True
                self._update_target = 'libs'
            if task == 'update:test:html':
                self._update = True
                self._update_test = True
                self._update_target = 'html'
            if task == 'update:test:javascript':
                self._update = True
                self._update_test = True
                self._update_target = 'javascript'
            if task == 'upload':
                self._build = True
                self._zip = True
                self._upload = True
            if task == 'autodeploy':
                self._autodeploy = True
            if task == 'lint':
                self._lint = True

            if not self._build and not self._test and not self._deploy and not self._zip and not self._jsdoc and not self._update and not self._upload and not self._autodeploy and not self._lint:
                raise UnknownCommandError('The provided argument could not be recognized by the manage.py script: ' + task)

    def _execute_subproject(self, project):
        if 'source' not in project:
            return

        if self._build or self._autodeploy:
            options = self._gather_option_string(project)
            print('Building sub-project located at: ' + project['source']['url'])
            if project['source']['type'] == 'file':
                sourcedir = project['source']['url']
                self._build_subproject(sourcedir, project['destination'], options)
            if project['source']['type'] == 'git':
                sourcedir = mkdtemp()
                try:
                    repo = git.Repo.clone_from(project['source']['url'], sourcedir, branch=project['source']['branch'])
                    self._build_subproject(sourcedir, project['destination'], options)
                except git.exc.GitCommandNotFound:
                    raise UnknownCommandError('Could not find the git executable in your path. Please make sure git is installed and accessible through the console.')
                except git.exc.GitCommandError as e:
                    raise UnknownCommandError('Something went wrong while executing git.')
                except:
                    raise UnknownCommandError('Failed for an unknown reason. Please try again.')
                finally:
                    rmtree(sourcedir)
            if project['source']['type'] == 'tar' or project['source']['type'] == 'zip' or project['source']['type'] == 'tar.gz':
                sourcedir = mkdtemp()
                sourcearchive = os.path.join(sourcedir, 'source')
                projecttype = project['source']['type']
                try:
                    r = requests.get(project['source']['url'], stream=True)

                    with open(sourcearchive, '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()

                    if projecttype == 'tar':
                        tf = tarfile.open(sourcearchive, mode='r')
                        dirname = os.path.join(sourcedir, tf.getnames()[0])
                        tf.extractall(sourcedir)
                    if projecttype == 'tar.gz':
                        tf = tarfile.open(sourcearchive, mode='r:gz')
                        dirname = os.path.join(sourcedir, tf.getnames()[0])
                        tf.extractall(sourcedir)
                    if projecttype == 'zip':
                        z = zipfile.ZipFile(sourcearchive, 'r')
                        dirname = os.path.join(sourcedir, z.namelist()[0])
                        z.extractall(sourcedir)

                    self._build_subproject(dirname, project['destination'], options)

                except requests.exceptions.ConnectionError as e:
                    raise GeneralError('Could not download the source tar file.')
                    return
                finally:
                    rmtree(sourcedir)

    def _build_subproject(self, path, destination, options):
        cwd = os.getcwd()
        if not os.path.isabs(path):
            path = os.path.abspath(path)
        if not os.path.isabs(destination):
            destination = os.path.abspath(destination)
        os.chdir(path)
        options += ' -o zip_path=' + destination
        args = 'python manage.py zip ' + options

        if not os.path.exists(destination):
            os.makedirs(destination)

        n = open(os.devnull, 'w')
        try:
            popen = subprocess.Popen(shlex.split(args), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            output, error = popen.communicate()

            if error != b'':
                raise Exception()
        except:
            raise SubProjectError('Could not execute the sub project at location: "' + path + '". Pleas try again.')
        finally:
            n.close()
            os.chdir(cwd)

    def _gather_option_string(self, project):
        string = ''

        if 'options' in project:
            for key, value in project['options'].items():
                if key != 'zip_path':
                    string += '-o ' + key + '=' + value + ' '

        string += '-o autolint=false'
        return string

    def _build_subprojects(self):
        subproject_infos = os.path.join(os.path.expanduser('~'), '.grace', 'subprojects.json')
        if len(self._config['embedded_projects']) > 0:
            with open(subproject_infos, 'w+') as f:
                try:
                    infos = json.load(f)
                except:
                    infos = {
                        'parent': os.getcwd()
                    }

            new_subprojects = []
            for project in self._config['embedded_projects']:
                build_it = True
                if infos['parent'] == project['source']['url']:
                    build_it = False
                else:
                    if 'subprojects' in infos:
                        for processed_project in infos['subprojects']:
                            if processed_project == project['source']:
                                build_it = False
                                break

                new_subprojects.append(project['source'])

                if build_it:
                    infos['subprojects'] = new_subprojects

                    if os.path.exists(subproject_infos):
                        os.remove(subproject_infos)

                    with open(subproject_infos, 'w+') as f:
                        f.write(json.dumps(infos))

                    self._execute_subproject(project)

            if self._build or self._autodeploy:
                print('')

        with open(subproject_infos, 'w+') as f:
            try:
                infos = json.load(f)
            except:
                infos = {}

        if 'parent' in infos:
            if infos['parent'] == os.getcwd():
                os.remove(subproject_infos)
        else:
            os.remove(subproject_infos)

    def execute(self):
        self._build_subprojects()

        if self._clean:
            clean()
            return

        if self._build:
            if self._config['autolint']:
                valid = self.exec_lint(False)
                if not valid:
                    print('The JavaScript could not be linted and therefore no building will happen. Fix all errors mentioned by autolint (or be evil and remove the autolint option).')
                    return

            self.exec_build()

            self._config['build'] = True

            if self._deploy:
                self.exec_deploy(None)
            if self._zip:
                self.exec_zip(None)

                if self._upload:
                    self.exec_upload()

        if self._test:
            if not os.path.exists(os.path.join(os.getcwd(), 'test')):
                print('No tests to build found.')
                return
            else:
                if not os.path.exists(os.path.join(os.getcwd(), 'test', 'tests')):
                    print('No tests to build found.')
                    return

            if self._test_cases is None:
                self._test_cases = self._config['test_cases']

            if self._test_cases is None:
                self._test_cases = []
                for test_case in os.listdir(os.path.join(os.getcwd(), 'test', 'tests')):
                    self._test_cases.append(test_case[5:-3])

            for test_case in self._test_cases:
                self.exec_test(test_case)

                self._config['test'] = True

                if self._deploy:
                    self.exec_deploy(test_case)
                if self._zip:
                    self.exec_zip(test_case)

        if self._jsdoc:
            self.exec_jsdoc()

        if self._update:
            self.exec_update(self._update_target)

        if self._autodeploy:
            print('Watching the source directory for any changes ... (Hit Ctrl+c to exit)\n')

            autodeploy_dir = os.path.join(os.path.expanduser('~'), '.grace', 'autodeploy')
            if not os.path.exists(autodeploy_dir):
                os.mkdir(autodeploy_dir)
            else:
                rmtree(autodeploy_dir)
                os.mkdir(autodeploy_dir)

            self._config['build'] = True
            src_dir = os.path.join(os.getcwd(), 'src')
            event_handler = ChangeHandler(self.exec_autodeploy)
            observer = Observer()
            observer.schedule(event_handler, src_dir, recursive=True)
            observer.start()

            try:
                while True:
                    time.sleep(1)
            except KeyboardInterrupt:
                observer.stop()

            observer.join()

        if self._lint:
            print('Linting the source directory.')

            self.exec_lint()

    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)
        else:
            u = Upload(self._config)

        u.run()

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

    def exec_lint(self, silent=False):
        if self._module is not None:
            try:
                l = getattr(self._module.plugin, 'Lint')(self._config)
            except AttributeError:
                l = Lint(self._config)
        else:
            l = Lint(self._config)

        if not silent:
            print('Linting js files in source directory ...')

        try:
            l.run()
        except NoExectuableError:
            if not silent:
                print('No node executable found. Make sure either nodejs or node is executable from the command line.')
            return True

        return l.lint_valid

    def exec_update(self, target):
        print('Please be aware that an update will replace anything you have done to the files.')
        ack = input('Continue: yes/[no] ')

        if ack != 'yes':
            print('Canceling update.')
            sys.exit()

        if self._module is not None:
            try:
                u = getattr(self._module.plugin, 'Update')(self._config, target)
            except AttributeError:
                u = Update(self._config, self._update_test, target)
        else:
            u = Update(self._config, self._update_test, target)

        u.run()

        print('Successfully updated the project.') 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 Assets(object):
    def __init__(self, projectName):
        self._projectName = projectName
        self._root = get_path()
        self._cwd = os.getcwd()

        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._projectPath = os.path.join(self._cwd, self._projectName)

        self._copy_assets()

    def _copy_assets(self):
        if not os.path.exists(self._assetPath):
            raise FileNotFoundError('Could not find the manage.py asset.')

        try:
            copy(self._assetPath, os.path.join(self._cwd, self._projectName))
        except:
            raise FileNotWritableError('Could not create the manage.py file.') class Config(object):
    def __init__(self):
        self._config = {}

        try:
            self._config = self._load_configurations()
            self._parse_subprojects()
        except:
            raise

        if 'type' not in self._config:
            self._config['type'] = 'default'
        else:
            if not isstring(self._config['type']):
                self._config['type'] = 'default'
            else:
                if len(self._config['type']) == 0:
                    self._config['type'] = 'default'

    def get_type(self):
        return self._config['type']

    def load_overwrites(self, overwrites):
        self._config = self._update_config(self._config, overwrites)

    def get_config(self):
        self._parse_config()
        return self._config

    def _parse_subprojects(self):
        if 'embedded_projects' not in self._config:
            self._config['embedded_projects'] = []

        for index, project in enumerate(self._config['embedded_projects']):
            if 'source' in project:
                if isinstance(project['source'], dict):
                    if 'url' not in project['source']:
                        raise MissingKeyError('The provided source is a dict but no url was defined under it.')
                    if 'type' not in project['source']:
                        url = project['source']['url']
                        urltype = 'git'
                        if url.endswith('tar.gz'):
                            urltype = 'tar.gz'
                        if url.endswith('tar'):
                            urltype = 'tar'
                        if url.endswith('zip'):
                            urltype = 'zip'
                        if url.endswith('git'):
                            urltype = 'git'
                        if os.path.exists(url):
                            urltype = 'file'

                        self._config['embedded_projects'][index]['source']['type'] = urltype
                    else:
                        urltype = project['source']['type']
                        if not (urltype == 'tar.gz' or urltype == 'tar' or urltype == 'zip' or urltype == 'git'):
                            raise WrongFormatError('The provided url type is not either zip, tar, tar.gz or git.')
                    if 'branch' not in project['source']:
                        self._config['embedded_projects'][index]['source']['branch'] = 'master'
                    else:
                        if not isstring(project['source']['branch']):
                            raise WrongFormatError('The provided branch to check out is not a string.')
                elif isstring(project['source']):
                    url = project['source']
                    urltype = 'git'
                    if url.endswith('tar.gz'):
                        urltype = 'tar.gz'
                    if url.endswith('tar'):
                        urltype = 'tar'
                    if url.endswith('zip'):
                        urltype = 'zip'
                    if url.endswith('git'):
                        urltype = 'git'
                    if os.path.exists(url):
                        urltype = 'file'
                    branch = 'master'

                    self._config['embedded_projects'][index]['source'] = {
                        'url': url,
                        'type': urltype,
                        'branch': branch
                    }

                if 'destination' not in project:
                    raise MissingKeyError('The config file defines a source, but no destination was given.')
                else:
                    if not isstring(project['destination']):
                        raise WrongFormatError('Destination has to be a string.')

                if 'options' not in project:
                    self._config['embedded_projects'][index]['options'] = {}

                if self._config['embedded_projects'][index]['source']['type'] == 'file':
                    self._config['embedded_projects'][index]['source']['url'] = os.path.abspath(self._config['embedded_projects'][index]['source']['url'])

    def _load_configurations(self):
        config_file = None
        cwd = os.getcwd()
        local_config = None
        global_config = None
        config_path = os.path.join(os.path.expanduser('~'), '.grace', 'grace.cfg')

        try:
            config_file = open(os.path.join(config_path))
        except:
            self._global_config = {}
            print('No global configuration file found, using local one for all values.')
            return

        try:
            global_config = self._parse_config_file(config_file)
        except:
            raise

        try:
            config_file = open(os.path.join(cwd, 'project.cfg'))
        except:
            raise FileNotFoundError('Could not find a config file in this directory.')

        try:
            local_config = self._parse_config_file(config_file)
        except:
            raise

        return self._preparse_config(update(global_config, local_config))

    def _preparse_config(self, config):
        return config

    def _check_update_keys(self, updates):
        if 'name' in updates:
            raise KeyNotAllowedError('Can not overwrite the name configuration option.')
        if 'version' in updates:
            raise KeyNotAllowedError('Can not overwrite the version configuration option.')

    def _update_config(self, config, updates):
        try:
            self._check_update_keys(updates)
        except:
            raise

        return update(config, updates)

    def _parse_config_file(self, config_file):
        strings = []
        for line in config_file:
            if not re.search('^\s*//.*', line):
                strings.append(line.strip())

        try:
            return load_json(''.join(strings))
        except:
            raise WrongFormatError('The provided configuration file could not be parsed.')

    def _parse_config(self):
        cwd = os.getcwd()

        if 'name' not in self._config:
            raise MissingKeyError('Name of the project needs to be in the config file.')
        else:
            if not isstring(self._config['name']):
                raise WrongFormatError('The name key in your config file must be a string!')
            else:
                if len(self._config['name']) == 0:
                    raise WrongFormatError('The name key in your config file must be at least one character long.')

        if 'version' not in self._config:
            raise MissingKeyError('Please specify a version in your config file.')
        else:
            if not isstring(self._config['version']):
                raise WrongFormatError('The version key in your config file needs to be a string!')

        if 'minify_js' not in self._config:
            self._config['minify_js'] = False
        else:
            if not isinstance(self._config['minify_js'], bool):
                self._config['minify_js'] = False

        if 'minify_css' not in self._config:
            self._config['minify_css'] = False
        else:
            if not isinstance(self._config['minify_css'], bool):
                self._config['minify_css'] = False

        if 'linter' not in self._config:
            self._config['linter'] = 'jshint'
        else:
            if not isstring(self._config['linter']):
                raise WrongFormatError('The linter key has to be a string.')

            if self._config['linter'] != 'jslint':
                if self._config['linter'] != 'jshint':
                    raise WrongFormatError('The linter key has to be either "jslint", "jshint" or undfined (missing).')

        if 'lintoptions' not in self._config:
            self._config['lintoptions'] = {}
        else:
            if not isinstance(self._config['lintoptions'], dict):
                raise WrongFormatError('The lintoptions key has to be a dict (object).')

        if 'autolint' not in self._config:
            self._config['autolint'] = True
        else:
            if not isinstance(self._config['autolint'], bool):
                raise WrongFormatError('The autolint key has to be a boolean.')

        if 'js_name' not in self._config:
            self._config['js_name'] = 'application'
        else:
            if not isstring(self._config['js_name']):
                self._config['js_name'] = 'application'
            else:
                if len(self._config['js_name']) == 0:
                    self._config['js_name'] = 'application'

        if 'test_cases' not in self._config:
            self._config['test_cases'] = None

        self._config['build_path'] = os.path.join(cwd, 'build', self._config['name']) class CommandLineParser(object):
    def __init__(self):
        self._instantiate_parser()
        self._add_arguments()

    def _instantiate_parser(self):
        self._parser = argparse.ArgumentParser(description=textwrap.dedent(self._get_description()), epilog=textwrap.dedent(self._get_epilog()), formatter_class=argparse.RawDescriptionHelpFormatter)

    def _get_description(self):
        return '''\
Grace is a toolchain to work with rich JavaScript applications.
It provides several tools for developers to create applications
in a fast and clean manner.'''

    def _get_epilog(self):
        return '''\
Task Commands
-------------
The following tasks can be specified through the task command.
    build       Builds the project and places the output in ./build/ProjectName.
    deploy      First build and then deploy the project to the path
                specified in the deployment_path option in your project.cfg file.
    autodeploy  Execute a deploy task upon any change int the src directory.
    jsdoc       Build the jsDoc of the project.
    zip         Build and then zip the output and put it into the path
                specified by the zip_path option in your project.cfg file.
    clean       Clean the build output.
    test        Build all the tests.
    test:deploy Build and then deploy the tests.
    test:zip    Build and then zip the tests
    upload      Upload the project to the specified server.

Overwrite Commands
------------------
Most of the configuration options specified by either the project.cfg or the
global grace.cfg can be overwritten on the command line. They take the form:
option=new_value

The following options can be overwritten:
deployment_path
zip_path
doc_path
minify_js       Accepts true or false
minify_css      Accepts true or false
autolint        Accepts true or false
urls:upload
credentials:username
credentials:password

Example:
python manage.py deploy --overwrite deployment_path=/tmp/deployment --overwrite minify_js=true
python manage.py build -o minify_css=true


Further Reading
---------------
For more information visit https://www.github.com/mdiener/grace'
'''

    def _add_arguments(self):
        self._parser.add_argument('task', help='Executes the given task.')
        self._parser.add_argument('--test-cases', help='Build only the specified test cases (separated by a semicolon).')
        self._parser.add_argument('--overwrite', '-o', action='append', help='Overwrite the specified configuration option.')
        self._parser.add_argument('--stack-trace', '-s', action='store_true', help='Provides a full stack trace instead of just an error message.')

    def get_arguments(self):
        args = self._parser.parse_args()

        overwrites = {}

        if args.overwrite is not None:
            for overwrite in args.overwrite:
                overwrite = overwrite.split('=')

                if len(overwrite) != 1:
                    key = overwrite[0]
                    value = overwrite[1]

                    def parse_nested_key(holder, keychain, value):
                        if len(keychain) == 1:
                            holder[keychain[0]] = value
                        else:
                            if keychain[0] not in holder:
                                holder[keychain[0]] = {}
                            parse_nested_key(holder[keychain[0]], keychain[1:], value)

                        return holder

                    if len(key.split(':')) > 1:
                        keychain = key.split(':')
                        key = keychain[0]
                        value = parse_nested_key({}, keychain[1:], value)

                    try:
                        value = load_json(value)
                    except:
                        pass

                    overwrites[key] = value

        return args.task, args.test_cases, overwrites, args.stack_trace class FileNotFoundError(Error):
    pass class WrongFormatError(Error):
    pass class MissingKeyError(Error):
    pass class CreateFolderError(Error):
    pass class FolderNotFoundError(Error):
    pass class FileNotWritableError(Error):
    pass class RemoveFolderError(Error):
    pass class RemoveFileError(Error):
    pass class FolderAlreadyExistsError(Error):
    pass class ScssError(Error):
    pass class UnknownCommandError(Error):
    pass class WrongLoginCredentials(Error):
    pass class RemoteServerError(Error):
    pass class KeyNotAllowedError(Error):
    pass class SubProjectError(Error):
    pass class ParseError(Error):
    pass def isstring(string):
    if isinstance(string, str):
        return True

    if sys.version_info.major < 3:
        if isinstance(string, newstr):
            return True
        if isinstance(string, basestring):
            return True

    return False
from builtins import input
from past.builtins import basestring
from grace.task import Task
from grace.create import New, Assets
from grace.config import Config
from grace.cmdparse import CommandLineParser
from grace.error import FileNotFoundError, WrongFormatError, MissingKeyError, CreateFolderError, FolderNotFoundError, FileNotWritableError, RemoveFolderError, RemoveFileError, FolderAlreadyExistsError, ScssError, UnknownCommandError, WrongLoginCredentials, RemoteServerError, KeyNotAllowedError, SubProjectError, ParseError
from grace.utils import isstring
from shutil import copy, move
from pkg_resources import resource_filename
import sys
import os
import logging
import re
import requests
import pip
        print('Plugin: ' + pluginName)
        print('Skeleton: ' + skeleton)
        okay = input('Are the options above correct? [y]: ')

        if okay != 'y' and okay != '':
            print('\n')
            args = new_input('', '', '')

    return args


def plugin_name_input():
    name = 'default'
    plugins = get_installed_plugins()

    output = 'Select what type (plugin) of project you want to create:'

    if len(plugins) == 0:
        name = input(output + ' default. [default]: ')
        if name == '':
            name = 'default'
    else:
        plugin_string = ''
        for plugin in plugins:
            plugin_string += plugin + ', '
        plugin_string = plugin_string.rstrip(', ')

        if 'dizmo' in plugins:
            name = input(output + ' default, ' + plugin_string + '. [dizmo]: ')
            if name == '':
                name = 'dizmo'
        else:
            name = input(output + ' default, ' + plugin_string + '. [default]: ')
            if name == '':
                name = 'default'

    return name


def skeleton_input(module=None):
    skeletons = ['default']
    if module is not None:
        try:
            skeletons = getattr(module.plugin, 'get_skeleton_names')()
        except AttributeError:
            pass

    skeleton_string = ''
    for s in skeletons:
        skeleton_string += s + ', '
    skeleton_string = skeleton_string[:-2]

    print('Please provide an URL to a skeleton or chose from the list of known skeletons:')
    skeleton = input('Skeletons: ' + skeleton_string + '. [' + skeletons[0] + ']: ')
    if skeleton == '':
        skeleton = skeletons[0]

    return skeleton


def execute(*args):
    global_config()
    config = Config()
    parsedConfig = None
    module = None

    if config.get_type() is not 'default':
        try:
            module = __import__('grace-' + config.get_type() + '.plugin')
        except:
            print_error_msg('Could not load the module of type ' + config.get_type() + '.')
            return

        try:
            config = getattr(module.plugin, 'Config')()
        except AttributeError:
            pass
        except MissingKeyError as e:
            print(e.msg)
            return

    parser = None
    if config.get_type() is not 'default':
        try:
            parser = getattr(module.plugin, 'CommandLineParser')()
            task, test_cases, overwrites, show_stacktrace = parser.get_arguments()
        except AttributeError:
            parser = CommandLineParser()
            task, test_cases, overwrites, show_stacktrace = parser.get_arguments()
    else:
        parser = CommandLineParser()
        task, test_cases, overwrites, show_stacktrace = parser.get_arguments()

    try:
        config.load_overwrites(overwrites)
    except KeyNotAllowedError as e:
        print_error_msg(e.msg)
        return;

    try:
        parsedConfig = config.get_config()
    except (WrongFormatError, MissingKeyError, FileNotFoundError) as e:
        print_error_msg(e.msg)
        return

    if show_stacktrace:
        try:
            task = getattr(module.plugin, 'Task')(task, parsedConfig, module, test_cases)
        except AttributeError:
            task = Task(task, parsedConfig, module, test_cases)

        task.execute()
    else:
        try:
            task = getattr(module.plugin, 'Task')(task, parsedConfig, module, test_cases)
        except UnknownCommandError as e:
            print_error_msg(e.msg)
            return
        except AttributeError:
            try:

                task = Task(task, parsedConfig, module, test_cases)