#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright 2020 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function


__metaclass__ = type


DOCUMENTATION = """
module: cli_parse
author: Bradley Thornton (@cidrblock)
short_description: Parse cli output or text using a variety of parsers
description:
- Parse cli output or text using a variety of parsers
version_added: 1.0.0
options:
    command:
        type: str
        description:
        - The command to run on the host
    text:
        type: str
        description:
        - Text to be parsed
    parser:
        type: dict
        description:
        - Parser specific parameters
        required: True
        suboptions:
            name:
                type: str
                description:
                - The name of the parser to use
                required: True
            command:
                type: str
                description:
                - The command used to locate the parser's template
            os:
                type: str
                description:
                - Provide an operating system value to the parser
                - For `ntc_templates` parser, this should be in the supported
                  `<vendor>_<os>` format.
            template_path:
                type: str
                description:
                - Path of the parser template on the Ansible controller
                - This can be a relative or an absolute path
            vars:
                type: dict
                description:
                - Additional parser specific parameters
                - See the cli_parse user guide for examples of parser specific variables
                - U(https://docs.ansible.com/ansible/latest/network/user_guide/cli_parsing.html)
    set_fact:
        description:
        - Set the resulting parsed data as a fact
        type: str


notes:
- The default search path for a parser template is templates/{{ short_os }}_{{ command }}.{{ extension }}
- => short_os derived from ansible_network_os or ansible_distribution and set to lower case
- => command is the command passed to the module with spaces replaced with _
- => extension is specific to the parser used (native=yaml, textfsm=textfsm, ttp=ttp)
- The default Ansible search path for the templates directory is used for parser templates as well
- Some parsers may have additional configuration options available. See the parsers/vars key and the parser's documentation
- Some parsers require third-party python libraries be installed on the Ansible control node and a specific python version
- example Pyats requires pyats and genie and requires Python 3
- example ntc_templates requires ntc_templates
- example textfsm requires textfsm
- example ttp requires ttp
- example xml requires xml_to_dict
- Support of 3rd party python libraries is limited to the use of their public APIs as documented
- "Additional information and examples can be found in the parsing user guide:"
- https://docs.ansible.com/ansible/latest/network/user_guide/cli_parsing.html
"""


EXAMPLES = r"""

# Using the native parser

# -------------
# templates/nxos_show_interface.yaml
# - example: Ethernet1/1 is up
#   getval: '(?P<name>\S+) is (?P<oper_state>\S+)'
#   result:
#     "{{ name }}":
#         name: "{{ name }}"
#         state:
#         operating: "{{ oper_state }}"
#   shared: True
#
# - example: admin state is up, Dedicated Interface
#   getval: 'admin state is (?P<admin_state>\S+)'
#   result:
#     "{{ name }}":
#         name: "{{ name }}"
#         state:
#         admin: "{{ admin_state }}"
#
# - example: "  Hardware: Ethernet, address: 0000.5E00.5301 (bia 0000.5E00.5301)"
#   getval: '\s+Hardware: (?P<hardware>.*), address: (?P<mac>\S+)'
#   result:
#     "{{ name }}":
#         hardware: "{{ hardware }}"
#         mac_address: "{{ mac }}"

- name: Run command and parse with native
  ansible.utils.cli_parse:
    command: "show interface"
    parser:
      name: ansible.netcommon.native
    set_fact: interfaces_fact


- name: Pass text and template_path
  ansible.utils.cli_parse:
    text: "{{ previous_command['stdout'] }}"
    parser:
      name: ansible.netcommon.native
      template_path: "{{ role_path }}/templates/nxos_show_interface.yaml"


# Using the ntc_templates parser

# -------------
# The ntc_templates use 'vendor_platform' for the file name
# it will be derived from ansible_network_os if not provided
# example cisco.ios.ios => cisco_ios

- name: Run command and parse with ntc_templates
  ansible.utils.cli_parse:
    command: "show interface"
    parser:
      name: ansible.netcommon.ntc_templates
  register: parser_output

- name: Pass text and command
  ansible.utils.cli_parse:
    text: "{{ previous_command['stdout'] }}"
    parser:
      name: ansible.netcommon.ntc_templates
      command: show interface
  register: parser_output


# Using the pyats parser

# -------------
# The pyats parser uses 'os' to locate the appropriate parser
# it will be derived from ansible_network_os if not provided
# in the case of pyats: cisco.ios.ios => iosxe

- name: Run command and parse with pyats
  ansible.utils.cli_parse:
    command: "show interface"
    parser:
      name: ansible.netcommon.pyats
  register: parser_output

- name: Pass text and command
  ansible.utils.cli_parse:
    text: "{{ previous_command['stdout'] }}"
    parser:
      name: ansible.netcommon.pyats
      command: show interface
  register: parser_output

- name: Provide an OS to pyats to use an ios parser
  ansible.utils.cli_parse:
    text: "{{ previous_command['stdout'] }}"
    parser:
      name: ansible.netcommon.pyats
      command: show interface
      os: ios
  register: parser_output


# Using the textfsm parser

# -------------
# templates/nxos_show_version.textfsm
#
# Value UPTIME ((\d+\s\w+.s.,?\s?){4})
# Value LAST_REBOOT_REASON (.+)
# Value OS (\d+.\d+(.+)?)
# Value BOOT_IMAGE (.*)
# Value PLATFORM (\w+)
#
# Start
#   ^\s+(NXOS: version|system:\s+version)\s+${OS}\s*$$
#   ^\s+(NXOS|kickstart)\s+image\s+file\s+is:\s+${BOOT_IMAGE}\s*$$
#   ^\s+cisco\s+${PLATFORM}\s+[cC]hassis
#   ^\s+cisco\s+Nexus\d+\s+${PLATFORM}
#   # Cisco N5K platform
#   ^\s+cisco\s+Nexus\s+${PLATFORM}\s+[cC]hassis
#   ^\s+cisco\s+.+-${PLATFORM}\s*
#   ^Kernel\s+uptime\s+is\s+${UPTIME}
#   ^\s+Reason:\s${LAST_REBOOT_REASON} -> Record

- name: Run command and parse with textfsm
  ansible.utils.cli_parse:
    command: "show version"
    parser:
      name: ansible.utils.textfsm
  register: parser_output

- name: Pass text and command
  ansible.utils.cli_parse:
    text: "{{ previous_command['stdout'] }}"
    parser:
      name: ansible.utils.textfsm
      command: show version
  register: parser_output

# Using the ttp parser

# -------------
# templates/nxos_show_interface.ttp
#
# {{ interface }} is {{ state }}
# admin state is {{ admin_state }}{{ ignore(".*") }}

- name: Run command and parse with ttp
  ansible.utils.cli_parse:
    command: "show interface"
    parser:
      name: ansible.utils.ttp
    set_fact: new_fact_key

- name: Pass text and template_path
  ansible.utils.cli_parse:
    text: "{{ previous_command['stdout'] }}"
    parser:
      name: ansible.utils.ttp
      template_path: "{{ role_path }}/templates/nxos_show_interface.ttp"
  register: parser_output

# Using the XML parser

# -------------
- name: Run command and parse with xml
  ansible.utils.cli_parse:
    command: "show interface | xml"
    parser:
      name: ansible.utils.xml
  register: parser_output

- name: Pass text and parse with xml
  ansible.utils.cli_parse:
    text: "{{ previous_command['stdout'] }}"
    parser:
      name: ansible.utils.xml
  register: parser_output
"""

RETURN = r"""
parsed:
  description: The structured data resulting from the parsing of the text
  returned: always
  type: dict
  sample:
stdout:
  description: The output from the command run
  returned: when provided a command
  type: str
  sample:
stdout_lines:
  description: The output of the command run split into lines
  returned: when provided a command
  type: list
  elements: str
  sample:
"""
