func (sm *SpecMore) OperationCountsByTag() *histogram.Histogram {
	hist := histogram.NewHistogram("Operation Counts by Tag")
	hist.Bins = sm.TagsMap(false, true)
	hist.Inflate()
	return hist
}
OperationCountsByTag returns a histogram for operations by tag.
package openapi3
import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"regexp"
	"strings"
	oas3 "github.com/getkin/kin-openapi/openapi3"
	"github.com/grokify/gocharts/data/histogram"
	"github.com/grokify/gocharts/data/table"
	"github.com/grokify/gocharts/data/table/tabulator"
	"github.com/grokify/simplego/encoding/jsonutil"
	"github.com/grokify/simplego/net/urlutil"
	"github.com/grokify/simplego/type/stringsutil"
)
type Spec = oas3.T
type Operation = oas3.Operation
type SpecMore struct {
	Spec *Spec
}
func ReadSpecMore(path string, validate bool) (*SpecMore, error) {
	spec, err := ReadFile(path, validate)
	if err != nil {
		return nil, err
	}
	return &SpecMore{Spec: spec}, nil
}
func (sm *SpecMore) SchemasCount() int {
	if sm.Spec == nil {
		return -1
	} else if sm.Spec.Components.Schemas == nil {
		return 0
	}
	return len(sm.Spec.Components.Schemas)
}
func (sm *SpecMore) OperationsTable(columns *tabulator.ColumnSet, filterFunc func(path, method string, op *oas3.Operation) bool) (*table.Table, error) {
	return operationsTable(sm.Spec, columns, filterFunc)
}
func operationsTable(spec *Spec, columns *tabulator.ColumnSet, filterFunc func(path, method string, op *oas3.Operation) bool) (*table.Table, error) {
	if columns == nil {
		columns = OpTableColumnsDefault(false)
	}
	tbl := table.NewTable(spec.Info.Title)
	tbl.Columns = columns.DisplayTexts()
	specMore := SpecMore{Spec: spec}
	tgs, err := specMore.TagGroups()
	if err != nil {
		return nil, err
	}
	VisitOperations(spec, func(path, method string, op *oas3.Operation) {
		if filterFunc != nil &&
			!filterFunc(path, method, op) {
			return
		}
		row := []string{}
		for _, text := range columns.Columns {
			switch text.Slug {
			case "tags":
				row = append(row, strings.Join(op.Tags, ", "))
			case "method":
				row = append(row, method)
			case "path":
				row = append(row, path)
			case "operationId":
				row = append(row, op.OperationID)
			case "summary":
				row = append(row, op.Summary)
			case XTagGroups:
				row = append(row, strings.Join(
					tgs.GetTagGroupNamesForTagNames(op.Tags...), ", "))
			case "securityScopes":
				row = append(row, strings.Join(OperationSecurityScopes(op, false), ", "))
			case XThrottlingGroup:
				row = append(row, GetExtensionPropStringOrEmpty(op.ExtensionProps, XThrottlingGroup))
			case "docsURL":
				if op.ExternalDocs != nil {
					row = append(row, op.ExternalDocs.URL)
				}
			default:
				row = append(row, GetExtensionPropStringOrEmpty(op.ExtensionProps, text.Slug))
			}
		}
		tbl.Rows = append(tbl.Rows, row)
	})
	return &tbl, nil
}
func OpTableColumnsDefault(inclDocsURL bool) *tabulator.ColumnSet {
	cols := []tabulator.Column{
		{
			Display: "Tags",
			Slug:    "tags",
			Width:   150},
		{
			Display: "Method",
			Slug:    "method",
			Width:   70},
		{
			Display: "Path",
			Slug:    "path",
			Width:   800},
		{
			Display: "OperationID",
			Slug:    "operationId",
			Width:   150},
		{
			Display: "Summary",
			Slug:    "summary",
			Width:   150},
		{
			Display: "SecurityScopes",
			Slug:    "securityScopes",
			Width:   150},
		{
			Display: "XThrottlingGroup",
			Slug:    XThrottlingGroup,
			Width:   150},
	}
	if inclDocsURL {
		cols = append(cols, tabulator.Column{
			Display: "DocsURL",
			Slug:    "docsURL",
			Width:   150})
	}
	return &tabulator.ColumnSet{Columns: cols}
}
func OpTableColumnsRingCentral() *tabulator.ColumnSet {
	columns := OpTableColumnsDefault(false)
	rcCols := []tabulator.Column{
		{
			Display: "API Group",
			Slug:    "x-api-group",
			Width:   150},
		{
			Display: "Throttling",
			Slug:    "x-throttling-group",
			Width:   150},
		{
			Display: "App Permission",
			Slug:    "x-app-permission",
			Width:   150},
		{
			Display: "User Permissions",
			Slug:    "x-user-permission",
			Width:   150},
	}
	columns.Columns = append(columns.Columns, rcCols...)
	return columns
}
func (sm *SpecMore) OperationMetas() []OperationMeta {
	ometas := []OperationMeta{}
	if sm.Spec == nil {
		return ometas
	}
	for url, path := range sm.Spec.Paths {
		if path.Connect != nil {
			ometas = append(ometas, OperationToMeta(url, http.MethodConnect, path.Connect))
		}
		if path.Delete != nil {
			ometas = append(ometas, OperationToMeta(url, http.MethodDelete, path.Delete))
		}
		if path.Get != nil {
			ometas = append(ometas, OperationToMeta(url, http.MethodGet, path.Get))
		}
		if path.Head != nil {
			ometas = append(ometas, OperationToMeta(url, http.MethodHead, path.Head))
		}
		if path.Options != nil {
			ometas = append(ometas, OperationToMeta(url, http.MethodOptions, path.Options))
		}
		if path.Patch != nil {
			ometas = append(ometas, OperationToMeta(url, http.MethodPatch, path.Patch))
		}
		if path.Post != nil {
			ometas = append(ometas, OperationToMeta(url, http.MethodPost, path.Post))
		}
		if path.Put != nil {
			ometas = append(ometas, OperationToMeta(url, http.MethodPut, path.Put))
		}
		if path.Trace != nil {
			ometas = append(ometas, OperationToMeta(url, http.MethodTrace, path.Trace))
		}
	}
	return ometas
}
func (sm *SpecMore) OperationsCount() int {
	if sm.Spec == nil {
		return -1
	}
	return len(sm.OperationMetas())
}