private void buildFromDocstring(@NotNull final PsiElement elementDefinition, boolean isProperty) {
    PyClass pyClass = null;
    final PyStringLiteralExpression docStringExpression =
        ((PyDocStringOwner) elementDefinition).getDocStringExpression();

    if (elementDefinition instanceof PyClass) {
      pyClass = (PyClass) elementDefinition;
      myBody.add(PythonDocumentationProvider.describeDecorators(pyClass, TagItalic, BR, LCombUp));
      myBody.add(PythonDocumentationProvider.describeClass(pyClass, TagBold, true, false));
    } else if (elementDefinition instanceof PyFunction) {
      PyFunction pyFunction = (PyFunction) elementDefinition;
      if (!isProperty) {
        pyClass = pyFunction.getContainingClass();
        if (pyClass != null) {
          myBody
              .addWith(
                  TagSmall, PythonDocumentationProvider.describeClass(pyClass, TagCode, true, true))
              .addItem(BR)
              .addItem(BR);
        }
      }
      myBody
          .add(PythonDocumentationProvider.describeDecorators(pyFunction, TagItalic, BR, LCombUp))
          .add(PythonDocumentationProvider.describeFunction(pyFunction, TagBold, LCombUp));
      if (docStringExpression == null) {
        addInheritedDocString(pyFunction, pyClass);
      }
    } else if (elementDefinition instanceof PyFile) {
      addModulePath((PyFile) elementDefinition);
    }
    if (docStringExpression != null) {
      myBody.addItem(BR);
      addFormattedDocString(myElement, docStringExpression.getStringValue(), myBody, myEpilog);
    }
  }
  private static void addFormattedDocString(
      PsiElement element,
      @NotNull String docstring,
      ChainIterable<String> formattedOutput,
      ChainIterable<String> unformattedOutput) {
    final Project project = element.getProject();

    List<String> formatted = PyStructuredDocstringFormatter.formatDocstring(element, docstring);
    if (formatted != null) {
      unformattedOutput.add(formatted);
      return;
    }

    boolean isFirstLine;
    final List<String> result = new ArrayList<String>();
    String[] lines = removeCommonIndentation(docstring);

    // reconstruct back, dropping first empty fragment as needed
    isFirstLine = true;
    int tabSize = CodeStyleSettingsManager.getSettings(project).getTabSize(PythonFileType.INSTANCE);
    for (String line : lines) {
      if (isFirstLine && ourSpacesPattern.matcher(line).matches())
        continue; // ignore all initial whitespace
      if (isFirstLine) {
        isFirstLine = false;
      } else {
        result.add(BR);
      }
      int leadingTabs = 0;
      while (leadingTabs < line.length() && line.charAt(leadingTabs) == '\t') {
        leadingTabs++;
      }
      if (leadingTabs > 0) {
        line = StringUtil.repeatSymbol(' ', tabSize * leadingTabs) + line.substring(leadingTabs);
      }
      result.add(combUp(line));
    }
    formattedOutput.add(result);
  }
  public PyDocumentationBuilder(PsiElement element, PsiElement originalElement) {
    myElement = element;
    myOriginalElement = originalElement;
    myResult = new ChainIterable<String>();
    myProlog = new ChainIterable<String>();
    myBody = new ChainIterable<String>();
    myEpilog = new ChainIterable<String>();

    myResult
        .add(myProlog)
        .addWith(TagCode, myBody)
        .add(myEpilog); // pre-assemble; then add stuff to individual cats as needed
    myResult = wrapInTag("html", wrapInTag("body", myResult));
    myReassignmentChain = new ChainIterable<String>();
  }
  public String build() {
    final TypeEvalContext context =
        TypeEvalContext.userInitiated(myElement.getProject(), myElement.getContainingFile());
    final PsiElement outerElement =
        myOriginalElement != null ? myOriginalElement.getParent() : null;

    final PsiElement elementDefinition = resolveToDocStringOwner();
    final boolean isProperty = buildFromProperty(elementDefinition, outerElement, context);

    if (myProlog.isEmpty() && !isProperty && !isAttribute()) {
      myProlog.add(myReassignmentChain);
    }

    if (elementDefinition instanceof PyDocStringOwner) {
      buildFromDocstring(elementDefinition, isProperty);
    } else if (isAttribute()) {
      buildFromAttributeDoc();
    } else if (elementDefinition instanceof PyNamedParameter) {
      buildFromParameter(context, outerElement, elementDefinition);
    } else if (elementDefinition != null && outerElement instanceof PyReferenceExpression) {
      myBody.addItem(combUp("\nInferred type: "));
      PythonDocumentationProvider.describeExpressionTypeWithLinks(
          myBody, (PyReferenceExpression) outerElement, context);
    }

    if (elementDefinition != null
        && PythonDialectsTokenSetProvider.INSTANCE
            .getKeywordTokens()
            .contains(elementDefinition.getNode().getElementType())) {
      buildForKeyword(elementDefinition.getText());
    }
    if (myBody.isEmpty() && myEpilog.isEmpty()) {
      return null; // got nothing substantial to say!
    } else {
      return myResult.toString();
    }
  }