# Copyright 2016 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.



import os
import tempfile
import textwrap
import unittest
# internal imports

from google.protobuf import text_format
from skydoc import build_pb2
from skydoc import macro_extractor


class MacroExtractorTest(unittest.TestCase):

  def check_protos(self, src, expected):
    tf = tempfile.NamedTemporaryFile(mode='w+', delete=False)
    tf.write(src)
    tf.flush()
    tf.close()

    expected_proto = build_pb2.BuildLanguage()
    text_format.Merge(expected, expected_proto)

    extractor = macro_extractor.MacroDocExtractor()
    extractor.parse_bzl(tf.name)
    proto = extractor.proto()
    os.remove(tf.name)
    self.assertEqual(expected_proto, proto)

  def test_multi_line_description(self):
    src = textwrap.dedent("""\
        def multiline(name, foo=False, visibility=None):
          \"\"\"A rule with multiline documentation.

          Some more documentation about this rule here.

          Args:
            name: A unique name for this rule.
            foo: A test argument.

              Documentation for foo continued here.
            visibility: The visibility of this rule.

              Documentation for visibility continued here.
          \"\"\"
          native.genrule(
              name = name,
              out = ["foo"],
              cmd = "touch $@",
              visibility = visibility,
          )
        """)

    expected = textwrap.dedent("""\
        rule {
          name: "multiline"
          documentation: "A rule with multiline documentation.\\n\\nSome more documentation about this rule here."
          attribute {
            name: "name"
            type: UNKNOWN
            mandatory: true
            documentation: "A unique name for this rule."
          }
          attribute {
            name: "foo"
            type: BOOLEAN
            mandatory: false
            default: "False"
            documentation: "A test argument.\\n\\nDocumentation for foo continued here."
          }
          attribute {
            name: "visibility"
            type: UNKNOWN
            mandatory: false
            documentation: "The visibility of this rule.\\n\\nDocumentation for visibility continued here."
          }
          type: MACRO
        }
        """)

    self.check_protos(src, expected)

  def test_kwargs(self):
    src = textwrap.dedent("""\
        def with_keyword_args(name, foo=False, **kwargs):
          \"\"\"A rule with keyword args.

          Args:
            name: A unique name for this rule.
            foo: A test argument.
            **kwargs: Keyword arguments.
          \"\"\"
          native.genrule(
              name = name,
              out = ["foo"],
              cmd = "touch $@",
              visibility = visibility,
              **kwargs
          )
        """)

    expected = textwrap.dedent("""\
        rule {
          name: "with_keyword_args"
          documentation: "A rule with keyword args."
          attribute {
            name: "name"
            type: UNKNOWN
            mandatory: true
            documentation: "A unique name for this rule."
          }
          attribute {
            name: "foo"
            type: BOOLEAN
            mandatory: false
            default: "False"
            documentation: "A test argument."
          }
          attribute {
            name: "**kwargs"
            type: UNKNOWN
            mandatory: false
            documentation: "Keyword arguments."
          }
          type: MACRO
        }
        """)

    self.check_protos(src, expected)

  def test_undocumented(self):
    src = textwrap.dedent("""\
        def undocumented(name, visibility=None):
          native.genrule(
              name = name,
              out = ["foo"],
              cmd = "touch $@",
              visibility = visibility,
          )
        """)

    expected = textwrap.dedent("""\
        rule {
          name: "undocumented"
          attribute {
            name: "name"
            type: UNKNOWN
            mandatory: true
          }
          attribute {
            name: "visibility"
            type: UNKNOWN
            mandatory: false
          }
          type: MACRO
        }
        """)

    self.check_protos(src, expected)

  def test_private_macros_skipped(self):
    src = textwrap.dedent("""\
        def _private(name, visibility=None):
          \"\"\"A private macro that should not appear in docs.

          Args:
            name: A unique name for this rule.
            visibility: The visibility of this rule.
          \"\"\"
          native.genrule(
              name = name,
              out = ["foo"],
              cmd = "touch $@",
              visibility = visibility,
          )

        def public(name, visibility=None):
          \"\"\"A public macro that should appear in docs.

          Args:
            name: A unique name for this rule.
            visibility: The visibility of this rule.
          \"\"\"
          native.genrule(
              name = name,
              out = ["foo"],
              cmd = "touch $@",
              visibility = visibility,
          )
        """)

    expected = textwrap.dedent("""\
        rule {
          name: "public"
          documentation: "A public macro that should appear in docs."
          attribute {
            name: "name"
            type: UNKNOWN
            mandatory: true
            documentation: "A unique name for this rule."
          }
          attribute {
            name: "visibility"
            type: UNKNOWN
            mandatory: false
            documentation: "The visibility of this rule."
          }
          type: MACRO
        }
        """)

    self.check_protos(src, expected)

  def test_rule_macro_mix(self):
    src = textwrap.dedent("""\
        def _impl(ctx):
          return struct()

        example_rule = rule(
            implementation = _impl,
            attrs = {
                "arg_label": attr.label(),
                "arg_string": attr.string(),
            },
        )
        \"\"\"An example rule.

        Args:
          name: A unique name for this rule.
          arg_label: A label argument.
          arg_string: A string argument.
        \"\"\"

        def example_macro(name, foo, visibility=None):
          \"\"\"An example macro.

          Args:
            name: A unique name for this rule.
            foo: A test argument.
            visibility: The visibility of this rule.
          \"\"\"
          native.genrule(
              name = name,
              out = ["foo"],
              cmd = "touch $@",
              visibility = visibility,
          )
        """)

    expected = textwrap.dedent("""\
        rule {
          name: "example_macro"
          documentation: "An example macro."
          attribute {
            name: "name"
            type: UNKNOWN
            mandatory: true
            documentation: "A unique name for this rule."
          }
          attribute {
            name: "foo"
            type: UNKNOWN
            mandatory: true
            documentation: "A test argument."
          }
          attribute {
            name: "visibility"
            type: UNKNOWN
            mandatory: false
            documentation: "The visibility of this rule."
          }
          type: MACRO
        }
        """)

    self.check_protos(src, expected)

  def test_macro_with_example_doc(self):
    src = textwrap.dedent("""\
        def macro_with_example(name, foo, visibility=None):
          \"\"\"Macro with examples.

          Args:
            name: A unique name for this rule.
            foo: A test argument.
            visibility: The visibility of this rule.

          Example:
            Here is an example of how to use this rule.
          \"\"\"
          native.genrule(
              name = name,
              out = ["foo"],
              cmd = "touch $@",
              visibility = visibility,
          )
        """)

    expected = textwrap.dedent("""\
        rule {
          name: "macro_with_example"
          documentation: "Macro with examples."
          example_documentation: "Here is an example of how to use this rule."
          attribute {
            name: "name"
            type: UNKNOWN
            mandatory: true
            documentation: "A unique name for this rule."
          }
          attribute {
            name: "foo"
            type: UNKNOWN
            mandatory: true
            documentation: "A test argument."
          }
          attribute {
            name: "visibility"
            type: UNKNOWN
            mandatory: false
            documentation: "The visibility of this rule."
          }
          type: MACRO
        }
        """)

    self.check_protos(src, expected)

  def test_macro_with_output_doc(self):
    src = textwrap.dedent("""\
        def macro_with_outputs(name, foo, visibility=None):
          \"\"\"Macro with output documentation.

          Outputs:
            %{name}.jar: A Java archive.
            %{name}_deploy.jar: A Java archive suitable for deployment.

                Only built if explicitly requested.

          Args:
            name: A unique name for this rule.
            foo: A test argument.
            visibility: The visibility of this rule.
          \"\"\"
          native.genrule(
              name = name,
              out = ["foo"],
              cmd = "touch $@",
              visibility = visibility,
          )
        """)

    expected = textwrap.dedent("""\
        rule {
          name: "macro_with_outputs"
          documentation: "Macro with output documentation."
          attribute {
            name: "name"
            type: UNKNOWN
            mandatory: true
            documentation: "A unique name for this rule."
          }
          attribute {
            name: "foo"
            type: UNKNOWN
            mandatory: true
            documentation: "A test argument."
          }
          attribute {
            name: "visibility"
            type: UNKNOWN
            mandatory: false
            documentation: "The visibility of this rule."
          }
          output {
            template: "%{name}.jar"
            documentation: "A Java archive."
          }
          output {
            template: "%{name}_deploy.jar"
            documentation: "A Java archive suitable for deployment.\\n\\nOnly built if explicitly requested."
          }
          type: MACRO
        }
        """)

    self.check_protos(src, expected)

  def test_file_doc_title_only(self):
    src = textwrap.dedent("""\
        \"\"\"Example rules\"\"\"
        """)
    tf = tempfile.NamedTemporaryFile(mode='w+', delete=False)
    tf.write(src)
    tf.flush()
    tf.close()

    extractor = macro_extractor.MacroDocExtractor()
    extractor.parse_bzl(tf.name)
    os.remove(tf.name)
    self.assertEqual('Example rules', extractor.title)
    self.assertEqual('', extractor.description)

  def test_file_doc_title_description(self):
    src = textwrap.dedent("""\
        \"\"\"Example rules

        This file contains example Bazel rules.

        Documentation continued here.
        \"\"\"
        """)
    tf = tempfile.NamedTemporaryFile(mode='w+', delete=False)
    tf.write(src)
    tf.flush()
    tf.close()

    extractor = macro_extractor.MacroDocExtractor()
    extractor.parse_bzl(tf.name)
    os.remove(tf.name)
    self.assertEqual('Example rules', extractor.title)
    self.assertEqual('This file contains example Bazel rules.'
                     '\n\nDocumentation continued here.',
                     extractor.description)

  def test_file_doc_title_multiline(self):
    src = textwrap.dedent("""\
        \"\"\"Example rules
        for Bazel

        This file contains example Bazel rules.

        Documentation continued here.
        \"\"\"
        """)
    tf = tempfile.NamedTemporaryFile(mode='w+', delete=False)
    tf.write(src)
    tf.flush()
    tf.close()

    extractor = macro_extractor.MacroDocExtractor()
    extractor.parse_bzl(tf.name)
    os.remove(tf.name)
    self.assertEqual('Example rules for Bazel', extractor.title)
    self.assertEqual('This file contains example Bazel rules.'
                     '\n\nDocumentation continued here.',
                     extractor.description)

  def test_loads_ignored(self):
    src = textwrap.dedent("""\
      load("//foo/bar:baz.bzl", "foo_library")
      load("//foo/bar:baz.bzl", "foo_test", orig_foo_binary = "foo_binary")
      """)
    expected = ''
    self.check_protos(src, expected)

if __name__ == '__main__':
  unittest.main()
