Example #1
0
 def name(self) -> str:
     name_to_skill = {skill.name: skill for skill in self.msm.list()}
     while True:
         name = ask_input(
             'Enter a short unique skill name (ie. "siren alarm" or "pizza orderer"):',
             lambda x: re.match(r'^[a-zA-Z \-]+$', x),
             'Please use only letter and spaces.').strip(
                 ' -').lower().replace(' ', '-')
         skill = name_to_skill.get(
             name, name_to_skill.get('{}-skill'.format(name)))
         if skill:
             print('The skill {} {}already exists'.format(
                 skill.name,
                 'by {} '.format(skill.author) * bool(skill.author)))
             if ask_yes_no('Remove it? (y/N)', False):
                 rmtree(skill.path)
             else:
                 continue
         class_name = '{}Skill'.format(to_camel(name.replace('-', '_')))
         repo_name = '{}-skill'.format(name)
         print()
         print('Class name:', class_name)
         print('Repo name:', repo_name)
         print()
         alright = ask_yes_no('Looks good? (Y/n)', True)
         if alright:
             return name
Example #2
0
class TestCreator(GlobalContext):
    def __init__(self, folder):
        self.folder = folder

    init_file = Lazy(lambda s: join(s.folder, '__init__.py'))
    init_content = Lazy(lambda s: read_file(s.init_file)
                        if isfile(s.init_file) else '')
    utterance = Lazy(
        lambda s: ask_input('Enter an example query:', lambda x: x))
    dialogs = Lazy(lambda s: [
        splitext(basename(i))[0]
        for i in glob(join(s.folder, 'dialog', s.lang, '*.dialog'))
    ])
    expected_dialog = Lazy(
        lambda s: ask_choice('Choose expected dialog (leave empty to skip).',
                             s.dialogs,
                             allow_empty=True,
                             on_empty='No dialogs available. Skipping...'))

    padatious_creator = Lazy(
        lambda s: PadatiousTestCreator(s.folder))  # type: PadatiousTestCreator
    adapt_creator = Lazy(
        lambda s: AdaptTestCreator(s.folder))  # type: AdaptTestCreator
    intent_choices = Lazy(lambda s: list(
        chain(s.adapt_creator.intent_recipes, s.padatious_creator.intent_names)
    ))

    @Lazy
    def intent_name(self):
        return ask_choice(
            'Which intent would you like to test?',
            self.intent_choices,
            on_empty='No existing intents found. Please create some first')
Example #3
0
 def license(self):
     """Ask user to select a license for the repo."""
     license_files = get_licenses()
     print('For uploading a skill a license is required.\n'
           'Choose one of the licenses listed below or add one later.\n')
     for num, pth in zip(range(1, 1 + len(license_files)), license_files):
         print('{}: {}'.format(num, pretty_license(pth)))
     choice = ask_input('Choose license above or press Enter to skip?')
     if choice.isdigit():
         index = int(choice) - 1
         shutil.copy(license_files[index], join(self.path, 'LICENSE.md'))
         print('\nSome of these require that you insert the project name '
               'and/or author\'s name. Please check the license file and '
               'add the appropriate information.\n')
Example #4
0
    def utterance_data(self) -> dict:
        utterance_data = {}
        utterance_left = self.utterance

        print()
        print('=== Entity Tags ===')
        for entity_name in sorted(self.entity_names):
            entity_value = ask_input(
                entity_name + ':', lambda x: not x or x in utterance_left,
                'Response must be in the remaining utterance: ' +
                utterance_left)
            if entity_value:
                utterance_data[entity_name] = entity_value
                utterance_left = utterance_left.replace(entity_value, '')
        return utterance_data
Example #5
0
    def utterance(self):
        while True:
            utterance = ask_input('Enter an example query:', lambda x: x)

            missing_vocabs = [
                i for i in self.intent_recipe['require']
                if not any(j in utterance.lower()
                           for j in self.vocab_defs.get(i, []))
            ]
            if missing_vocabs:
                print('Missing the following vocab:',
                      ', '.join(missing_vocabs))
                if ask_yes_no('Continue anyways? (y/N)', False):
                    return utterance
            else:
                return utterance
Example #6
0
    def utterance_data(self):
        utterance_left = self.utterance.lower()
        utterance_data = {}

        for key, start_message in [('require', 'Required'),
                                   ('optionally', 'Optional')]:
            if not self.intent_recipe[key]:
                continue

            print()
            print('===', start_message, 'Tags', '===')
            for vocab_name in sorted(self.intent_recipe[key]):
                vocab_value = ask_input(
                    vocab_name + ':',
                    lambda x: not x or x.lower() in utterance_left,
                    'Response must be in the remaining utterance: ' +
                    utterance_left)
                if vocab_value:
                    utterance_data[vocab_name] = vocab_value
                    utterance_left = utterance_left.replace(
                        vocab_value.lower(), '')

        return utterance_data
Example #7
0
class CreateAction(ConsoleAction):
    def __init__(self, args, name: str = None):
        if name:
            self.name = name

    @staticmethod
    def register(parser: ArgumentParser):
        pass

    @Lazy
    def name(self) -> str:
        name_to_skill = {skill.name: skill for skill in self.msm.list()}
        while True:
            name = ask_input(
                'Enter a short unique skill name (ie. "siren alarm" or "pizza orderer"):',
                lambda x: re.match(r'^[a-zA-Z \-]+$', x),
                'Please use only letter and spaces.').strip(
                    ' -').lower().replace(' ', '-')
            skill = name_to_skill.get(
                name, name_to_skill.get('{}-skill'.format(name)))
            if skill:
                print('The skill {} {}already exists'.format(
                    skill.name,
                    'by {} '.format(skill.author) * bool(skill.author)))
                if ask_yes_no('Remove it? (y/N)', False):
                    rmtree(skill.path)
                else:
                    continue
            class_name = '{}Skill'.format(to_camel(name.replace('-', '_')))
            repo_name = '{}-skill'.format(name)
            print()
            print('Class name:', class_name)
            print('Repo name:', repo_name)
            print()
            alright = ask_yes_no('Looks good? (Y/n)', True)
            if alright:
                return name

    path = Lazy(lambda s: join(s.msm.skills_dir, s.name + '-skill'))
    git = Lazy(lambda s: Git(s.path))
    short_description = Lazy(lambda s: ask_input(
        'Enter a one line description for your skill (ie. Orders fresh pizzas from the store):',
    ).capitalize())
    author = Lazy(lambda s: ask_input('Enter author:'))
    intent_lines = Lazy(lambda s: [
        i.capitalize() for i in ask_input_lines(
            'Enter some example phrases to trigger your skill:', '-')
    ])
    dialog_lines = Lazy(lambda s: [
        i.capitalize() for i in ask_input_lines(
            'Enter what your skill should say to respond:', '-')
    ])
    intent_entities = Lazy(lambda s: set(
        re.findall(r'(?<={)[a-z_A-Z]*(?=})', '\n'.join(
            i for i in s.intent_lines))))
    dialog_entities = Lazy(lambda s: set(
        re.findall(r'(?<={)[a-z_A-Z]*(?=})', '\n'.join(s.dialog_lines))))
    long_description = Lazy(
        lambda s: '\n\n'.join(ask_input_lines('Enter a long description:', '>')
                              ).strip().capitalize())
    readme = Lazy(lambda s: readme_template.format(
        title_name=s.name.replace('-', ' ').title(),
        short_description=s.short_description,
        long_description=s.long_description,
        examples=''.join(' - "{}"\n'.format(i) for i in s.intent_lines),
        credits=credits_template.format(author=s.author)))
    init_file = Lazy(lambda s: init_template.format(
        class_name=to_camel(s.name.replace('-', '_')),
        handler_name=s.intent_name.replace('.', '_'),
        handler_code='\n'.join(' ' * 8 * bool(i) + i for i in [
            "{ent} = message.data['{ent}']".format(ent=entity)
            for entity in sorted(s.intent_entities)
        ] + [
            "{ent} = ''".format(ent=entity)
            for entity in sorted(s.dialog_entities - s.intent_entities)
        ] + [''] * bool(
            s.dialog_entities | s.intent_entities
        ) + "self.speak_dialog('{intent}'{args})".format(
            intent=s.intent_name,
            args=", data={{\n{}\n}}".format(
                ',\n'.join("    '{ent}': {ent}".format(ent=entity)
                           for entity in s.dialog_entities | s.intent_entities)
            ) * bool(s.dialog_entities | s.intent_entities)).split('\n')),
        intent_name=s.intent_name))
    intent_name = Lazy(lambda s: '.'.join(reversed(s.name.split('-'))))

    def add_vocab(self):
        makedirs(join(self.path, 'vocab', self.lang))
        with open(
                join(self.path, 'vocab', self.lang,
                     self.intent_name + '.intent'), 'w') as f:
            f.write('\n'.join(self.intent_lines + ['']))

    def add_dialog(self):
        makedirs(join(self.path, 'dialog', self.lang))
        with open(
                join(self.path, 'dialog', self.lang,
                     self.intent_name + '.dialog'), 'w') as f:
            f.write('\n'.join(self.dialog_lines + ['']))

    def initialize_template(self, files: set = None):
        git = Git(self.path)

        skill_template = [
            ('', lambda: makedirs(self.path)), ('vocab', self.add_vocab),
            ('dialog', self.add_dialog),
            ('__init__.py', lambda: self.init_file),
            ('README.md', lambda: self.readme),
            ('.gitignore', lambda: gitignore_template),
            ('settingsmeta.json', lambda: settingsmeta_template.format(
                capital_desc=self.name.replace('-', ' ').capitalize())),
            ('.git', lambda: git.init())
        ]

        def cleanup():
            rmtree(self.path)

        if not isdir(self.path):
            atexit.register(cleanup)
        for file, handler in skill_template:
            if files and file not in files:
                continue
            if not exists(join(self.path, file)):
                result = handler()
                if isinstance(result,
                              str) and not exists(join(self.path, file)):
                    with open(join(self.path, file), 'w') as f:
                        f.write(result)
        atexit.unregister(cleanup)

    def commit_changes(self):
        if self.git.rev_parse('HEAD', with_exceptions=False) == 'HEAD':
            self.git.add('.')
            self.git.commit(message='Initial commit')

    def create_github_repo(self,
                           get_repo_name: Callable = None
                           ) -> Optional[Repository]:
        if 'origin' not in Git(self.path).remote().split('\n'):
            if ask_yes_no(
                    'Would you like to create a GitHub repo for it? (Y/n)',
                    True):
                repo_name = (get_repo_name
                             and get_repo_name()) or (self.name + '-skill')
                try:
                    repo = self.user.create_repo(repo_name,
                                                 self.short_description)
                except GithubException as e:
                    if e.status == 422:
                        raise GithubRepoExists(repo_name) from e
                    raise
                self.git.remote('add', 'origin', repo.html_url)
                call(['git', 'push', '-u', 'origin', 'master'],
                     cwd=self.git.working_dir)
                print('Created GitHub repo:', repo.html_url)
                return repo
        return None

    def perform(self):
        self.initialize_template()
        self.commit_changes()
        with print_error(GithubRepoExists):
            self.create_github_repo()
        print('Created skill at:', self.path)
Example #8
0
class CreateAction(ConsoleAction):
    def __init__(self, args, name: str = None):
        colorama_init()
        if name:
            self.name = name

    @staticmethod
    def register(parser: ArgumentParser):
        pass

    @Lazy
    def name(self) -> str:
        name_to_skill = {skill.name: skill for skill in self.msm.list()}
        while True:
            name = ask_input(
                'Enter a short unique skill name (ie. "siren alarm" or "pizza orderer"):',
                lambda x: re.match(r'^[a-zA-Z \-]+$', x),
                'Please use only letter and spaces.').strip(
                    ' -').lower().replace(' ', '-')
            skill = name_to_skill.get(
                name, name_to_skill.get('{}-skill'.format(name)))
            if skill:
                print('The skill {} {}already exists'.format(
                    skill.name,
                    'by {} '.format(skill.author) * bool(skill.author)))
                if ask_yes_no('Remove it? (y/N)', False):
                    rmtree(skill.path)
                else:
                    continue
            class_name = '{}Skill'.format(to_camel(name.replace('-', '_')))
            repo_name = '{}-skill'.format(name)
            print()
            print('Class name:', class_name)
            print('Repo name:', repo_name)
            print()
            alright = ask_yes_no('Looks good? (Y/n)', True)
            if alright:
                return name

    path = Lazy(lambda s: join(s.msm.skills_dir, s.name + '-skill'))
    git = Lazy(lambda s: Git(s.path))
    short_description = Lazy(lambda s: ask_input(
        'Enter a one line description for your skill (ie. Orders fresh pizzas from the store):\n-',
    ).capitalize())
    author = Lazy(lambda s: ask_input('Enter author:'))
    intent_lines = Lazy(lambda s: [
        i.capitalize() for i in ask_input_lines(
            'Enter some example phrases to trigger your skill:', '-')
    ])
    dialog_lines = Lazy(lambda s: [
        i.capitalize() for i in ask_input_lines(
            'Enter what your skill should say to respond:', '-')
    ])
    intent_entities = Lazy(lambda s: set(
        re.findall(r'(?<={)[a-z_A-Z]*(?=})', '\n'.join(
            i for i in s.intent_lines))))
    dialog_entities = Lazy(lambda s: set(
        re.findall(r'(?<={)[a-z_A-Z]*(?=})', '\n'.join(s.dialog_lines))))
    long_description = Lazy(
        lambda s: '\n\n'.join(ask_input_lines('Enter a long description:', '>')
                              ).strip().capitalize())
    icon = Lazy(lambda s: ask_input(
        'Go to Font Awesome ({blue}fontawesome.com/cheatsheet{reset}) and choose an icon.'
        '\nEnter the name of the icon (default: robot):'.format(
            blue=Fore.BLUE + Style.BRIGHT, reset=Style.RESET_ALL),
        validator=lambda x: x == '' or requests.get(
            "https://raw.githack.com/FortAwesome/Font-Awesome/"
            "master/svgs/solid/{x}.svg".format(x=x)).ok,
        on_fail=
        "\n\n{red}Error: The name was not found. Make sure you spelled the icon name right,"
        " and try again.{reset}\n".format(red=Fore.RED + Style.BRIGHT,
                                          reset=Style.RESET_ALL)))
    color = Lazy(lambda s: ask_input(
        "Pick a {yellow}color{reset} for your icon. Find a color that matches the color scheme at"
        " {blue}mycroft.ai/colors{reset}, or pick a color at: {blue}color-hex.com.{reset}"
        "\nEnter the color hex code including the # (default: #22A7F0):".
        format(blue=Fore.BLUE + Style.BRIGHT,
               yellow=Fore.YELLOW,
               reset=Style.RESET_ALL),
        validator=lambda hex_code: hex_code == '' or hex_code[
            0] == "#" and len(hex_code) in [4, 7],
        on_fail=
        "\n{red}Check that you entered a correct hex code, and try again.{reset}\n"
        .format(red=Fore.RED + Style.BRIGHT, reset=Style.RESET_ALL)))

    category_options = [
        'Daily', 'Configuration', 'Entertainment', 'Information', 'IoT',
        'Music & Audio', 'Media', 'Productivity', 'Transport'
    ]
    category_primary = Lazy(lambda s: s.ask_category_primary())
    categories_other = Lazy(
        lambda s: s.ask_categories_other(s.category_primary))

    tags = Lazy(lambda s: [
        i.capitalize() for i in ask_input_lines(
            'Enter tags to make it easier to search for your skill (optional):',
            '-')
    ])

    manifest = Lazy(lambda s: manifest_template if ask_yes_no(
        message=
        "Does this Skill depend on Python Packages (PyPI), System Packages (apt-get/others), or other skills?"
        "\nThis will create a manifest.yml file for you to define the dependencies for your Skill."
        "\nCheck the Mycroft documentation at mycroft.ai/to/skill-dependencies to learn more about including dependencies, and the manifest.yml file, in Skills. (y/N)",
        default=False) else None)

    readme = Lazy(lambda s: readme_template.format(
        title_name=s.name.replace('-', ' ').title(),
        short_description=s.short_description,
        long_description=s.long_description,
        examples=''.join('* "{}"\n'.format(i) for i in s.intent_lines),
        credits=credits_template.format(author=s.author),
        icon=s.icon or 'robot',
        color=s.color.upper() or '#22A7F0',
        category_primary=s.category_primary,
        categories_other=''.join('{}\n'.format(i) for i in s.categories_other),
        tags=''.join('#{}\n'.format(i) for i in s.tags),
    ))
    init_file = Lazy(lambda s: init_template.format(
        class_name=to_camel(s.name.replace('-', '_')),
        handler_name=s.intent_name.replace('.', '_'),
        handler_code='\n'.join(' ' * 8 * bool(i) + i for i in [
            "{ent} = message.data.get('{ent}')".format(ent=entity)
            for entity in sorted(s.intent_entities)
        ] + [
            "{ent} = ''".format(ent=entity)
            for entity in sorted(s.dialog_entities - s.intent_entities)
        ] + [''] * bool(
            s.dialog_entities | s.intent_entities
        ) + "self.speak_dialog('{intent}'{args})".format(
            intent=s.intent_name,
            args=", data={{\n{}\n}}".format(
                ',\n'.join("    '{ent}': {ent}".format(ent=entity)
                           for entity in s.dialog_entities | s.intent_entities)
            ) * bool(s.dialog_entities | s.intent_entities)).split('\n')),
        intent_name=s.intent_name))

    intent_name = Lazy(lambda s: '.'.join(reversed(s.name.split('-'))))

    def add_locale(self):
        makedirs(join(self.path, 'locale', self.lang))
        with open(
                join(self.path, 'locale', self.lang,
                     self.intent_name + '.intent'), 'w') as f:
            f.write('\n'.join(self.intent_lines + ['']))
        with open(
                join(self.path, 'locale', self.lang,
                     self.intent_name + '.dialog'), 'w') as f:
            f.write('\n'.join(self.dialog_lines + ['']))

    def ask_category_primary(self):
        """Ask user to select primary category."""
        category = ask_choice(
            '\nCategories define where the skill will display in the Marketplace. \nEnter the primary category for your skill: ',
            self.category_options,
            allow_empty=False)
        return category

    def ask_categories_other(self, category_primary):
        """Ask user to select aditional categories."""
        categories_other = []
        while True:
            category_options_formatted = []
            for category in self.category_options:
                if (category == category_primary) or (category
                                                      in categories_other):
                    category = '*' + category + '*'
                category_options_formatted.append(category)
            category = ask_choice('Enter additional categories (optional):',
                                  category_options_formatted,
                                  allow_empty=True,
                                  on_empty=None)
            if (category != None) and (category[0] != '*'):
                categories_other.append(category)
            if category == None:
                break
        return categories_other

    def license(self):
        """Ask user to select a license for the repo."""
        license_files = get_licenses()
        print('For uploading a skill a license is required.\n'
              'Choose one of the licenses listed below or add one later.\n')
        for num, pth in zip(range(1, 1 + len(license_files)), license_files):
            print('{}: {}'.format(num, pretty_license(pth)))
        choice = ask_input('Choose license above or press Enter to skip?')
        if choice.isdigit():
            index = int(choice) - 1
            shutil.copy(license_files[index], join(self.path, 'LICENSE.md'))
            print('\nSome of these require that you insert the project name '
                  'and/or author\'s name. Please check the license file and '
                  'add the appropriate information.\n')

    def initialize_template(self, files: set = None):
        git = Git(self.path)

        skill_template = [
            ('', lambda: makedirs(self.path)), ('locale', self.add_locale),
            ('__init__.py', lambda: self.init_file),
            ('README.md', lambda: self.readme), ('LICENSE.md', self.license),
            ('.gitignore', lambda: gitignore_template),
            ('settingsmeta.yaml', lambda: settingsmeta_template.format(
                capital_desc=self.name.replace('-', ' ').capitalize())),
            ('manifest.yml', lambda: self.manifest),
            ('.git', lambda: git.init())
        ]

        def cleanup():
            rmtree(self.path)

        if not isdir(self.path):
            atexit.register(cleanup)
        for file, handler in skill_template:
            if files and file not in files:
                continue
            if not exists(join(self.path, file)):
                result = handler()
                if isinstance(result,
                              str) and not exists(join(self.path, file)):
                    with open(join(self.path, file), 'w') as f:
                        f.write(result)
        atexit.unregister(cleanup)

    def commit_changes(self):
        if self.git.rev_parse('HEAD', with_exceptions=False) == 'HEAD':
            self.git.add('.')
            self.git.commit(message='Initial commit')

    def force_push(self,
                   get_repo_name: Callable = None) -> Optional[Repository]:
        if ask_yes_no(
                'Are you sure you want to overwrite the remote github repo? '
                'This cannot be undone and you will lose your commit '
                'history! (y/N)', False):
            repo_name = (get_repo_name
                         and get_repo_name()) or (self.name + '-skill')
            repo = self.user.get_repo(repo_name)
            self.git.push('origin', 'master', force=True)
            print('Force pushed to GitHub repo:', repo.html_url)
            return repo

    def link_github_repo(self,
                         get_repo_name: Callable = None
                         ) -> Optional[Repository]:
        if 'origin' not in Git(self.path).remote().split('\n'):
            if ask_yes_no(
                    'Would you like to link an existing GitHub repo to it? (Y/n)',
                    True):
                repo_name = (get_repo_name
                             and get_repo_name()) or (self.name + '-skill')
                repo = self.user.get_repo(repo_name)
                self.git.remote('add', 'origin', repo.html_url)
                self.git.fetch()
                try:
                    self.git.pull('origin', 'master')
                except GitCommandError as e:
                    if e.status == 128:
                        raise UnrelatedGithubHistory(repo_name) from e
                    raise
                self.git.push('origin', 'master', set_upstream=True)
                print('Linked and pushed to GitHub repo:', repo.html_url)
                return repo

    def create_github_repo(self,
                           get_repo_name: Callable = None
                           ) -> Optional[Repository]:
        if 'origin' not in Git(self.path).remote().split('\n'):
            if ask_yes_no(
                    'Would you like to create a GitHub repo for it? (Y/n)',
                    True):
                repo_name = (get_repo_name
                             and get_repo_name()) or (self.name + '-skill')
                try:
                    repo = self.user.create_repo(repo_name,
                                                 self.short_description)
                except GithubException as e:
                    if e.status == 422:
                        raise GithubRepoExists(repo_name) from e
                    raise
                self.git.remote('add', 'origin', repo.html_url)
                call(['git', 'push', '-u', 'origin', 'master'],
                     cwd=self.git.working_dir)
                print('Created GitHub repo:', repo.html_url)
                return repo
        return None

    def perform(self):
        self.initialize_template()
        self.commit_changes()
        with print_error(GithubRepoExists):
            self.create_github_repo()
        print('Created skill at:', self.path)