detector.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. package detector
  2. import (
  3. "regexp"
  4. "strings"
  5. )
  6. // DiagramType represents the type of diagram
  7. type DiagramType string
  8. const (
  9. DiagramTypeUnknown DiagramType = "unknown"
  10. DiagramTypeSequence DiagramType = "sequence"
  11. DiagramTypeState DiagramType = "state"
  12. DiagramTypeStateV2 DiagramType = "state-v2"
  13. DiagramTypeFlowchart DiagramType = "flowchart"
  14. DiagramTypeClass DiagramType = "class"
  15. DiagramTypeER DiagramType = "er"
  16. DiagramTypeGantt DiagramType = "gantt"
  17. DiagramTypePie DiagramType = "pie"
  18. DiagramTypeGit DiagramType = "git"
  19. DiagramTypeUserJourney DiagramType = "user-journey"
  20. DiagramTypeTimeline DiagramType = "timeline"
  21. DiagramTypeMindmap DiagramType = "mindmap"
  22. DiagramTypeKanban DiagramType = "kanban"
  23. DiagramTypeSankey DiagramType = "sankey"
  24. DiagramTypeXYChart DiagramType = "xychart"
  25. DiagramTypeQuadrant DiagramType = "quadrant-chart"
  26. DiagramTypeRequirement DiagramType = "requirement"
  27. DiagramTypeBlock DiagramType = "block"
  28. DiagramTypeC4 DiagramType = "c4"
  29. DiagramTypeArchitecture DiagramType = "architecture"
  30. DiagramTypeRadar DiagramType = "radar"
  31. DiagramTypeTreemap DiagramType = "treemap"
  32. DiagramTypePacket DiagramType = "packet"
  33. DiagramTypeInfo DiagramType = "info"
  34. )
  35. // DetectorFunc is a function that detects if text matches a specific diagram type
  36. type DetectorFunc func(text string) bool
  37. // DiagramDetector represents a diagram detector with its type and detection function
  38. type DiagramDetector struct {
  39. Type DiagramType
  40. Detector DetectorFunc
  41. Priority int // Higher priority = checked first
  42. }
  43. // DetectorRegistry manages all diagram detectors
  44. type DetectorRegistry struct {
  45. detectors []DiagramDetector
  46. }
  47. // NewDetectorRegistry creates a new detector registry with default detectors
  48. func NewDetectorRegistry() *DetectorRegistry {
  49. registry := &DetectorRegistry{
  50. detectors: make([]DiagramDetector, 0),
  51. }
  52. // Register default detectors in order of specificity (most specific first)
  53. registry.Register(sequenceDetector())
  54. registry.Register(stateV2Detector())
  55. registry.Register(stateDetector())
  56. registry.Register(flowchartDetector())
  57. registry.Register(classDetector())
  58. registry.Register(erDetector())
  59. registry.Register(ganttDetector())
  60. registry.Register(pieDetector())
  61. registry.Register(gitDetector())
  62. registry.Register(userJourneyDetector())
  63. registry.Register(timelineDetector())
  64. registry.Register(mindmapDetector())
  65. registry.Register(kanbanDetector())
  66. registry.Register(sankeyDetector())
  67. registry.Register(xyChartDetector())
  68. registry.Register(quadrantDetector())
  69. registry.Register(requirementDetector())
  70. registry.Register(blockDetector())
  71. registry.Register(c4Detector())
  72. registry.Register(architectureDetector())
  73. registry.Register(radarDetector())
  74. registry.Register(treemapDetector())
  75. registry.Register(packetDetector())
  76. registry.Register(infoDetector())
  77. return registry
  78. }
  79. // Register adds a diagram detector to the registry
  80. func (r *DetectorRegistry) Register(detector DiagramDetector) {
  81. r.detectors = append(r.detectors, detector)
  82. // Sort by priority (higher first)
  83. for i := len(r.detectors) - 1; i > 0; i-- {
  84. if r.detectors[i].Priority > r.detectors[i-1].Priority {
  85. r.detectors[i], r.detectors[i-1] = r.detectors[i-1], r.detectors[i]
  86. } else {
  87. break
  88. }
  89. }
  90. }
  91. // DetectDiagramType detects the type of diagram from the given text
  92. func (r *DetectorRegistry) DetectDiagramType(text string) DiagramType {
  93. // Clean the text by removing comments and directives
  94. cleanText := cleanText(text)
  95. for _, detector := range r.detectors {
  96. if detector.Detector(cleanText) {
  97. return detector.Type
  98. }
  99. }
  100. return DiagramTypeUnknown
  101. }
  102. // cleanText removes comments, directives, and front matter from text
  103. func cleanText(text string) string {
  104. // Remove front matter (YAML between --- markers)
  105. frontMatterRegex := regexp.MustCompile(`(?s)^---\s*\n(.*?)\n---\s*\n+`)
  106. text = frontMatterRegex.ReplaceAllString(text, "")
  107. // Remove directives (%%{...}%%)
  108. directiveRegex := regexp.MustCompile(`%%\{[^}]*\}%%`)
  109. text = directiveRegex.ReplaceAllString(text, "")
  110. // Remove comments (%% ...) - this will leave empty lines
  111. commentRegex := regexp.MustCompile(`(?m)^\s*%%.*$`)
  112. text = commentRegex.ReplaceAllString(text, "")
  113. // Remove multiple consecutive newlines and normalize whitespace
  114. text = regexp.MustCompile(`\n\s*\n+`).ReplaceAllString(text, "\n")
  115. // Remove leading/trailing whitespace from each line
  116. lines := strings.Split(text, "\n")
  117. for i, line := range lines {
  118. lines[i] = strings.TrimRight(line, " \t")
  119. }
  120. text = strings.Join(lines, "\n")
  121. // Remove leading/trailing whitespace from entire text
  122. text = strings.TrimSpace(text)
  123. return text
  124. }
  125. // Individual detector functions
  126. func sequenceDetector() DiagramDetector {
  127. return DiagramDetector{
  128. Type: DiagramTypeSequence,
  129. Priority: 100,
  130. Detector: func(text string) bool {
  131. matched, _ := regexp.MatchString(`(?i)^\s*sequenceDiagram`, text)
  132. return matched
  133. },
  134. }
  135. }
  136. func stateV2Detector() DiagramDetector {
  137. return DiagramDetector{
  138. Type: DiagramTypeStateV2,
  139. Priority: 95,
  140. Detector: func(text string) bool {
  141. matched, _ := regexp.MatchString(`(?i)^\s*stateDiagram-v2`, text)
  142. return matched
  143. },
  144. }
  145. }
  146. func stateDetector() DiagramDetector {
  147. return DiagramDetector{
  148. Type: DiagramTypeState,
  149. Priority: 90,
  150. Detector: func(text string) bool {
  151. matched, _ := regexp.MatchString(`(?i)^\s*stateDiagram`, text)
  152. return matched
  153. },
  154. }
  155. }
  156. func flowchartDetector() DiagramDetector {
  157. return DiagramDetector{
  158. Type: DiagramTypeFlowchart,
  159. Priority: 85,
  160. Detector: func(text string) bool {
  161. matched, _ := regexp.MatchString(`(?i)^\s*(?:flowchart|graph)`, text)
  162. return matched
  163. },
  164. }
  165. }
  166. func classDetector() DiagramDetector {
  167. return DiagramDetector{
  168. Type: DiagramTypeClass,
  169. Priority: 80,
  170. Detector: func(text string) bool {
  171. matched, _ := regexp.MatchString(`(?i)^\s*classDiagram`, text)
  172. return matched
  173. },
  174. }
  175. }
  176. func erDetector() DiagramDetector {
  177. return DiagramDetector{
  178. Type: DiagramTypeER,
  179. Priority: 75,
  180. Detector: func(text string) bool {
  181. matched, _ := regexp.MatchString(`(?i)^\s*erDiagram`, text)
  182. return matched
  183. },
  184. }
  185. }
  186. func ganttDetector() DiagramDetector {
  187. return DiagramDetector{
  188. Type: DiagramTypeGantt,
  189. Priority: 70,
  190. Detector: func(text string) bool {
  191. matched, _ := regexp.MatchString(`(?i)^\s*gantt`, text)
  192. return matched
  193. },
  194. }
  195. }
  196. func pieDetector() DiagramDetector {
  197. return DiagramDetector{
  198. Type: DiagramTypePie,
  199. Priority: 65,
  200. Detector: func(text string) bool {
  201. matched, _ := regexp.MatchString(`(?i)^\s*pie`, text)
  202. return matched
  203. },
  204. }
  205. }
  206. func gitDetector() DiagramDetector {
  207. return DiagramDetector{
  208. Type: DiagramTypeGit,
  209. Priority: 60,
  210. Detector: func(text string) bool {
  211. matched, _ := regexp.MatchString(`(?i)^\s*gitGraph`, text)
  212. return matched
  213. },
  214. }
  215. }
  216. func userJourneyDetector() DiagramDetector {
  217. return DiagramDetector{
  218. Type: DiagramTypeUserJourney,
  219. Priority: 55,
  220. Detector: func(text string) bool {
  221. matched, _ := regexp.MatchString(`(?i)^\s*journey`, text)
  222. return matched
  223. },
  224. }
  225. }
  226. func timelineDetector() DiagramDetector {
  227. return DiagramDetector{
  228. Type: DiagramTypeTimeline,
  229. Priority: 50,
  230. Detector: func(text string) bool {
  231. matched, _ := regexp.MatchString(`(?i)^\s*timeline`, text)
  232. return matched
  233. },
  234. }
  235. }
  236. func mindmapDetector() DiagramDetector {
  237. return DiagramDetector{
  238. Type: DiagramTypeMindmap,
  239. Priority: 45,
  240. Detector: func(text string) bool {
  241. matched, _ := regexp.MatchString(`(?i)^\s*mindmap`, text)
  242. return matched
  243. },
  244. }
  245. }
  246. func kanbanDetector() DiagramDetector {
  247. return DiagramDetector{
  248. Type: DiagramTypeKanban,
  249. Priority: 40,
  250. Detector: func(text string) bool {
  251. matched, _ := regexp.MatchString(`(?i)^\s*kanban`, text)
  252. return matched
  253. },
  254. }
  255. }
  256. func sankeyDetector() DiagramDetector {
  257. return DiagramDetector{
  258. Type: DiagramTypeSankey,
  259. Priority: 35,
  260. Detector: func(text string) bool {
  261. matched, _ := regexp.MatchString(`(?i)^\s*sankey-beta`, text)
  262. return matched
  263. },
  264. }
  265. }
  266. func xyChartDetector() DiagramDetector {
  267. return DiagramDetector{
  268. Type: DiagramTypeXYChart,
  269. Priority: 30,
  270. Detector: func(text string) bool {
  271. matched, _ := regexp.MatchString(`(?i)^\s*xychart-beta`, text)
  272. return matched
  273. },
  274. }
  275. }
  276. func quadrantDetector() DiagramDetector {
  277. return DiagramDetector{
  278. Type: DiagramTypeQuadrant,
  279. Priority: 25,
  280. Detector: func(text string) bool {
  281. matched, _ := regexp.MatchString(`(?i)^\s*quadrantChart`, text)
  282. return matched
  283. },
  284. }
  285. }
  286. func requirementDetector() DiagramDetector {
  287. return DiagramDetector{
  288. Type: DiagramTypeRequirement,
  289. Priority: 20,
  290. Detector: func(text string) bool {
  291. matched, _ := regexp.MatchString(`(?i)^\s*requirement`, text)
  292. return matched
  293. },
  294. }
  295. }
  296. func blockDetector() DiagramDetector {
  297. return DiagramDetector{
  298. Type: DiagramTypeBlock,
  299. Priority: 15,
  300. Detector: func(text string) bool {
  301. matched, _ := regexp.MatchString(`(?i)^\s*block-beta`, text)
  302. return matched
  303. },
  304. }
  305. }
  306. func c4Detector() DiagramDetector {
  307. return DiagramDetector{
  308. Type: DiagramTypeC4,
  309. Priority: 10,
  310. Detector: func(text string) bool {
  311. matched, _ := regexp.MatchString(`(?i)^\s*c4Context`, text)
  312. return matched
  313. },
  314. }
  315. }
  316. func architectureDetector() DiagramDetector {
  317. return DiagramDetector{
  318. Type: DiagramTypeArchitecture,
  319. Priority: 5,
  320. Detector: func(text string) bool {
  321. matched, _ := regexp.MatchString(`(?i)^\s*architecture`, text)
  322. return matched
  323. },
  324. }
  325. }
  326. func radarDetector() DiagramDetector {
  327. return DiagramDetector{
  328. Type: DiagramTypeRadar,
  329. Priority: 3,
  330. Detector: func(text string) bool {
  331. matched, _ := regexp.MatchString(`(?i)^\s*radar`, text)
  332. return matched
  333. },
  334. }
  335. }
  336. func treemapDetector() DiagramDetector {
  337. return DiagramDetector{
  338. Type: DiagramTypeTreemap,
  339. Priority: 2,
  340. Detector: func(text string) bool {
  341. matched, _ := regexp.MatchString(`(?i)^\s*treemap`, text)
  342. return matched
  343. },
  344. }
  345. }
  346. func packetDetector() DiagramDetector {
  347. return DiagramDetector{
  348. Type: DiagramTypePacket,
  349. Priority: 1,
  350. Detector: func(text string) bool {
  351. matched, _ := regexp.MatchString(`(?i)^\s*packet`, text)
  352. return matched
  353. },
  354. }
  355. }
  356. func infoDetector() DiagramDetector {
  357. return DiagramDetector{
  358. Type: DiagramTypeInfo,
  359. Priority: 0,
  360. Detector: func(text string) bool {
  361. matched, _ := regexp.MatchString(`(?i)^\s*info`, text)
  362. return matched
  363. },
  364. }
  365. }