mnogootex 0.2.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +5 -5
  2. data/.github/actions/setup-ruby/action.yml +34 -0
  3. data/.github/workflows/main.yml +44 -0
  4. data/.gitignore +3 -0
  5. data/.rspec +0 -2
  6. data/.rubocop.yml +15 -0
  7. data/CHANGELOG.md +55 -0
  8. data/CODE_OF_CONDUCT.md +1 -1
  9. data/Gemfile +4 -3
  10. data/Guardfile +56 -0
  11. data/README.md +260 -20
  12. data/Rakefile +25 -4
  13. data/demo/.mnogootexrc +4 -0
  14. data/demo/demo.asciicast +114 -0
  15. data/demo/demo.gif +0 -0
  16. data/demo/main.tex +5 -0
  17. data/exe/mnogootex +2 -92
  18. data/lib/mnogootex/cfg.rb +72 -0
  19. data/lib/mnogootex/cli.rb +63 -0
  20. data/lib/mnogootex/job/logger.rb +53 -0
  21. data/lib/mnogootex/job/porter.rb +63 -0
  22. data/lib/mnogootex/job/runner.rb +60 -0
  23. data/lib/mnogootex/job/warden.rb +104 -0
  24. data/lib/mnogootex/log/level.rb +17 -0
  25. data/lib/mnogootex/log/levels.yml +29 -0
  26. data/lib/mnogootex/log/line.rb +14 -0
  27. data/lib/mnogootex/log/matcher.rb +17 -0
  28. data/lib/mnogootex/log/matchers.yml +205 -0
  29. data/lib/mnogootex/log/processor.rb +115 -0
  30. data/lib/mnogootex/log.rb +23 -0
  31. data/lib/mnogootex/utils.rb +27 -0
  32. data/lib/mnogootex/version.rb +3 -1
  33. data/lib/mnogootex.rb +4 -4
  34. data/mnogootex.gemspec +43 -18
  35. data/spec/mnogootex/cfg_spec.rb +54 -0
  36. data/spec/mnogootex/job/porter_spec.rb +140 -0
  37. data/spec/mnogootex/job/runner_spec.rb +74 -0
  38. data/spec/mnogootex/log/processor_spec.rb +203 -0
  39. data/spec/mnogootex/utils_spec.rb +52 -0
  40. data/spec/spec_helper.rb +124 -0
  41. metadata +150 -29
  42. data/.gitmodules +0 -3
  43. data/.travis.yml +0 -5
  44. data/bin/console +0 -14
  45. data/bin/setup +0 -8
  46. data/lib/mnogootex/configuration.rb +0 -46
  47. data/lib/mnogootex/job.rb +0 -75
@@ -0,0 +1,114 @@
1
+ {"version": 2, "width": 92, "height": 26, "timestamp": 1637333757, "env": {"SHELL": "/bin/zsh", "TERM": "alacritty"}}
2
+ [0.017014, "o", "\u001b[?2004h$ "]
3
+ [0.816219, "o", "m"]
4
+ [0.880306, "o", "n"]
5
+ [1.120106, "o", "o"]
6
+ [1.328005, "o", "g"]
7
+ [1.431937, "o", "o"]
8
+ [1.560266, "o", "o"]
9
+ [1.664007, "o", "t"]
10
+ [1.824229, "o", "e"]
11
+ [2.096341, "o", "x"]
12
+ [2.208064, "o", " "]
13
+ [2.416225, "o", "b"]
14
+ [2.488119, "o", "u"]
15
+ [2.528114, "o", "i"]
16
+ [2.775867, "o", "l"]
17
+ [2.927851, "o", "d"]
18
+ [3.048131, "o", " "]
19
+ [3.18416, "o", "m"]
20
+ [3.30414, "o", "a"]
21
+ [3.360091, "o", "i"]
22
+ [3.463861, "o", "n"]
23
+ [3.664082, "o", "."]
24
+ [3.768087, "o", "t"]
25
+ [3.872092, "o", "e"]
26
+ [4.072065, "o", "x"]
27
+ [4.19206, "o", "\r\n\u001b[?2004l\r"]
28
+ [4.610511, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣾\u001b[0m\r"]
29
+ [4.630807, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣾\u001b[0m\r"]
30
+ [4.652109, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣾\u001b[0m\r"]
31
+ [4.672831, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣾\u001b[0m\r"]
32
+ [4.693597, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣾\u001b[0m\r"]
33
+ [4.71437, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣾\u001b[0m\r"]
34
+ [4.735132, "o", "Runners: \u001b[0;33;49m⣽\u001b[0m\u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣾\u001b[0m\r"]
35
+ [4.755441, "o", "Runners: \u001b[0;33;49m⣻\u001b[0m\u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣾\u001b[0m\r"]
36
+ [4.776142, "o", "Runners: \u001b[0;33;49m⢿\u001b[0m\u001b[0;33;49m⣽\u001b[0m\u001b[0;33;49m⣽\u001b[0m\r"]
37
+ [4.796857, "o", "Runners: \u001b[0;33;49m⡿\u001b[0m\u001b[0;33;49m⣻\u001b[0m\u001b[0;33;49m⣻\u001b[0m\r"]
38
+ [4.817161, "o", "Runners: \u001b[0;33;49m⣟\u001b[0m\u001b[0;33;49m⢿\u001b[0m\u001b[0;33;49m⢿\u001b[0m\r"]
39
+ [4.837458, "o", "Runners: \u001b[0;33;49m⣯\u001b[0m\u001b[0;33;49m⡿\u001b[0m\u001b[0;33;49m⡿\u001b[0m\r"]
40
+ [4.857789, "o", "Runners: \u001b[0;33;49m⣷\u001b[0m\u001b[0;33;49m⣟\u001b[0m\u001b[0;33;49m⣟\u001b[0m\r"]
41
+ [4.878122, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣯\u001b[0m\u001b[0;33;49m⣯\u001b[0m\r"]
42
+ [4.898448, "o", "Runners: \u001b[0;33;49m⣽\u001b[0m\u001b[0;33;49m⣷\u001b[0m\u001b[0;33;49m⣷\u001b[0m\r"]
43
+ [4.918754, "o", "Runners: \u001b[0;33;49m⣻\u001b[0m\u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣾\u001b[0m\r"]
44
+ [4.939493, "o", "Runners: \u001b[0;33;49m⢿\u001b[0m\u001b[0;33;49m⣽\u001b[0m\u001b[0;33;49m⣽\u001b[0m\r"]
45
+ [4.960303, "o", "Runners: \u001b[0;33;49m⡿\u001b[0m\u001b[0;33;49m⣻\u001b[0m\u001b[0;33;49m⣻\u001b[0m\r"]
46
+ [4.980995, "o", "Runners: \u001b[0;33;49m⣟\u001b[0m\u001b[0;33;49m⢿\u001b[0m\u001b[0;33;49m⢿\u001b[0m\r"]
47
+ [5.001721, "o", "Runners: \u001b[0;33;49m⣯\u001b[0m\u001b[0;33;49m⡿\u001b[0m\u001b[0;33;49m⡿\u001b[0m\r"]
48
+ [5.022407, "o", "Runners: \u001b[0;33;49m⣷\u001b[0m\u001b[0;33;49m⣟\u001b[0m\u001b[0;33;49m⣟\u001b[0m\r"]
49
+ [5.042661, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;33;49m⣯\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
50
+ [5.062924, "o", "Runners: \u001b[0;33;49m⣽\u001b[0m\u001b[0;33;49m⣷\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
51
+ [5.083219, "o", "Runners: \u001b[0;33;49m⣻\u001b[0m\u001b[0;33;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
52
+ [5.10347, "o", "Runners: \u001b[0;33;49m⢿\u001b[0m\u001b[0;33;49m⣽\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
53
+ [5.123727, "o", "Runners: \u001b[0;33;49m⡿\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
54
+ [5.143997, "o", "Runners: \u001b[0;33;49m⣟\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
55
+ [5.164261, "o", "Runners: \u001b[0;33;49m⣯\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
56
+ [5.18454, "o", "Runners: \u001b[0;33;49m⣷\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
57
+ [5.204898, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
58
+ [5.225157, "o", "Runners: \u001b[0;33;49m⣽\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
59
+ [5.245427, "o", "Runners: \u001b[0;33;49m⣻\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
60
+ [5.265709, "o", "Runners: \u001b[0;33;49m⢿\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
61
+ [5.285988, "o", "Runners: \u001b[0;33;49m⡿\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
62
+ [5.306276, "o", "Runners: \u001b[0;33;49m⣟\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
63
+ [5.326587, "o", "Runners: \u001b[0;33;49m⣯\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
64
+ [5.346862, "o", "Runners: \u001b[0;33;49m⣷\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
65
+ [5.367137, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
66
+ [5.387471, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
67
+ [5.407789, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
68
+ [5.428094, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
69
+ [5.448427, "o", "Runners: \u001b[0;33;49m⣽\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
70
+ [5.468685, "o", "Runners: \u001b[0;33;49m⣻\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
71
+ [5.48902, "o", "Runners: \u001b[0;33;49m⢿\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
72
+ [5.509286, "o", "Runners: \u001b[0;33;49m⡿\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
73
+ [5.529559, "o", "Runners: \u001b[0;33;49m⣟\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
74
+ [5.549834, "o", "Runners: \u001b[0;33;49m⣯\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
75
+ [5.570114, "o", "Runners: \u001b[0;33;49m⣷\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
76
+ [5.590495, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
77
+ [5.610773, "o", "Runners: \u001b[0;33;49m⣽\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
78
+ [5.631053, "o", "Runners: \u001b[0;33;49m⣻\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
79
+ [5.651328, "o", "Runners: \u001b[0;33;49m⢿\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
80
+ [5.671634, "o", "Runners: \u001b[0;33;49m⡿\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
81
+ [5.691885, "o", "Runners: \u001b[0;33;49m⣟\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
82
+ [5.712144, "o", "Runners: \u001b[0;33;49m⣯\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
83
+ [5.732409, "o", "Runners: \u001b[0;33;49m⣷\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
84
+ [5.752725, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
85
+ [5.77299, "o", "Runners: \u001b[0;33;49m⣽\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
86
+ [5.793269, "o", "Runners: \u001b[0;33;49m⣻\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
87
+ [5.81354, "o", "Runners: \u001b[0;33;49m⢿\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
88
+ [5.833802, "o", "Runners: \u001b[0;33;49m⡿\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
89
+ [5.854091, "o", "Runners: \u001b[0;33;49m⣟\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
90
+ [5.874667, "o", "Runners: \u001b[0;33;49m⣯\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
91
+ [5.894903, "o", "Runners: \u001b[0;33;49m⣷\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
92
+ [5.915157, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
93
+ [5.935432, "o", "Runners: \u001b[0;33;49m⣽\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
94
+ [5.955706, "o", "Runners: \u001b[0;33;49m⣻\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
95
+ [5.975985, "o", "Runners: \u001b[0;33;49m⢿\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
96
+ [5.996281, "o", "Runners: \u001b[0;33;49m⡿\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
97
+ [6.016565, "o", "Runners: \u001b[0;33;49m⣟\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
98
+ [6.03685, "o", "Runners: \u001b[0;33;49m⣯\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
99
+ [6.057124, "o", "Runners: \u001b[0;33;49m⣷\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
100
+ [6.077406, "o", "Runners: \u001b[0;33;49m⣾\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
101
+ [6.097789, "o", "Runners: \u001b[0;33;49m⣽\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
102
+ [6.118073, "o", "Runners: \u001b[0;33;49m⣻\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r"]
103
+ [6.138511, "o", "Runners: \u001b[0;32;49m⣯\u001b[0m\u001b[0;31;49m⣾\u001b[0m\u001b[0;31;49m⢿\u001b[0m\r\r\nOutcome:\r\n"]
104
+ [6.138712, "o", " \u001b[0;32;49m✔\u001b[0m scrarticle\r\n \u001b[0;31;49m✘\u001b[0m article\r\n"]
105
+ [6.139693, "o", " \u001b[0;97;49mLatexmk: This is Latexmk, John Collins, 21 September 2021, version: 4.75.\u001b[0m\r\n \u001b[0;97;49mThis is pdfTeX, Version 3.141592653-2.6-1.40.23 (TeX Live 2021) (preloaded format=latex)\u001b[0m\r\n \u001b[0;91;49m! Undefined control sequence.\u001b[0m\r\n \u001b[0;91;49ml.4 Let's port my \\KOMAScript\u001b[0m\r\n \u001b[0;91;49m \\ article!\u001b[0m\r\n \u001b[0;97;49mOutput written on main.dvi (1 page, 424 bytes).\u001b[0m\r\n \u001b[0;91;49mLatexmk: Errors, so I did not complete making targets\u001b[0m\r\n \u001b[0;31;49m✘\u001b[0m book\r\n"]
106
+ [6.140624, "o", " \u001b[0;97;49mLatexmk: This is Latexmk, John Collins, 21 September 2021, version: 4.75.\u001b[0m\r\n \u001b[0;97;49mThis is pdfTeX, Version 3.141592653-2.6-1.40.23 (TeX Live 2021) (preloaded format=latex)\u001b[0m\r\n \u001b[0;91;49m! Undefined control sequence.\u001b[0m\r\n \u001b[0;91;49ml.3 \\abstract\u001b[0m\r\n \u001b[0;91;49m {Simply put, my article is awesome.}\u001b[0m\r\n \u001b[0;91;49m! Undefined control sequence.\u001b[0m\r\n \u001b[0;91;49ml.4 Let's port my \\KOMAScript\u001b[0m\r\n"]
107
+ [6.140752, "o", " \u001b[0;91;49m \\ article!\u001b[0m\r\n \u001b[0;97;49mOutput written on main.dvi (1 page, 324 bytes).\u001b[0m\r\n \u001b[0;91;49mLatexmk: Errors, so I did not complete making targets\u001b[0m\r\n"]
108
+ [6.145564, "o", "\u001b[?2004h$ "]
109
+ [9.623727, "o", "e"]
110
+ [9.720091, "o", "x"]
111
+ [9.904131, "o", "i"]
112
+ [10.016153, "o", "t"]
113
+ [10.496203, "o", "\r\n\u001b[?2004l\r"]
114
+ [10.49658, "o", "exit\r\n"]
data/demo/demo.gif ADDED
Binary file
data/demo/main.tex ADDED
@@ -0,0 +1,5 @@
1
+ \documentclass{scrarticle}
2
+ \begin{document}
3
+ \abstract{Simply put, my article is awesome.}
4
+ Let's port my \KOMAScript\ article!
5
+ \end{document}
data/exe/mnogootex CHANGED
@@ -1,96 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
- # coding: utf-8
2
+ # frozen_string_literal: true
3
3
 
4
4
  require 'mnogootex'
5
5
 
6
-
7
- # require 'pathname'
8
-
9
- # target = ARGV[0]
10
- # target_path = File.expand_path target
11
- # cfg = Mnogootex::Configuration.new
12
- # cfg.load target_path
13
-
14
- require 'tmpdir'
15
- require 'fileutils'
16
- require 'open3'
17
-
18
- require 'colorize'
19
-
20
- target = ARGV[0]
21
-
22
-
23
- raise "No parameters given." if ARGV.length.zero?
24
-
25
- if ARGV.length == 3
26
- raise "Unknown command." unless %w{show view open}.include? ARGV[1].downcase
27
- job = Mnogootex::Job.new cls: ARGV[2], target: File.expand_path(target)
28
- pdf = Dir.glob("#{job.tmp_dirname}/*.pdf").first
29
- raise "PDF non esiste." unless File.exist? pdf
30
- `command -v open >/dev/null && open #{pdf} || xdg-open #{pdf}`
31
- exit
32
- end
33
-
34
- puts "Mnogootex v#{Mnogootex::VERSION}"
35
-
36
- main_path = File.expand_path(target)
37
- main_basename = File.basename main_path
38
- main_dirname = File.dirname main_path
39
-
40
- cfg = Mnogootex::Configuration.new
41
- cfg.load main_dirname
42
-
43
- raise "File non esiste." unless File.exist? main_path
44
-
45
- @documentclasses = cfg['compile_with']
46
-
47
- $jobs = []
48
- $threads = []
49
- $draw_threads = []
50
-
51
- $threads = []
52
-
53
- $anim = cfg['animation'].freeze
54
-
55
- STDOUT.sync = true
56
-
57
- def draw_status
58
- icons = $jobs.map do |j|
59
- icon = $anim[j.ticks % $anim.length]
60
- case j.thread.status
61
- when 'sleep', 'run', 'aborting'
62
- icon.yellow
63
- when false, nil # exited (normally or w/ error)
64
- j.success? ? icon.green : icon.red
65
- end
66
- end
67
- print ' Jobs: ' + icons.join + "\r"
68
- end
69
-
70
- draw_status
71
-
72
- @documentclasses.each_with_index do |cls, index|
73
- job = Mnogootex::Job.new cls: cls, target: main_path
74
- job.setup
75
- job.run
76
-
77
- $jobs << job
78
-
79
- $draw_threads << job.tick_thread
80
- $threads << job.thread
81
- end
82
-
83
- $threads.map(&:join)
84
- $draw_threads.map(&:join)
85
-
86
- puts
87
-
88
- puts ' Details:'
89
- $jobs.each do |job|
90
- if job.success?
91
- puts ' ' + "✔".green + ' ' + File.basename(job.cls)
92
- else
93
- puts ' ' + "✘".red + ' ' + File.basename(job.cls)
94
- puts job.log[2..-2].join.gsub(/^/,' '*6).chomp.red
95
- end
96
- end
6
+ Mnogootex::CLI.start ARGV
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'pathname'
5
+
6
+ module Mnogootex
7
+ module Cfg
8
+ BASENAME = '.mnogootexrc'
9
+ DEFAULTS = {
10
+ 'jobs' => [],
11
+ 'spinner' => '⣾⣽⣻⢿⡿⣟⣯⣷',
12
+ 'work_path' => nil,
13
+ }.freeze
14
+
15
+ def self.load_descending(pathname:, basename:)
16
+ pathname.realpath.descend.
17
+ map { |path| path.join(basename) }.
18
+ select(&:exist?).reject(&:zero?).
19
+ map { |path| YAML.load_file(path) }.
20
+ reduce(&:merge!)
21
+ end
22
+
23
+ def self.recombobulate(*args)
24
+ try_args(*args) || try_link(*args) || try_cfgs(*args)
25
+ end
26
+
27
+ class << self
28
+ private
29
+
30
+ def split_jobs_and_flags(args)
31
+ # TODO: some kind of validation?
32
+ flags = args.drop_while { |arg| !arg.start_with?('-') }
33
+ jobs = args.take_while { |arg| !arg.start_with?('-') }
34
+ [(jobs unless jobs.empty?), (flags unless flags.empty?)]
35
+ end
36
+
37
+ def try_args(*args)
38
+ main = Pathname.new(args.fetch(-1, ''))
39
+ return unless main.file?
40
+
41
+ main = main.realpath
42
+ cfg = load_descending(pathname: main.dirname, basename: BASENAME)
43
+ jobs, flags = split_jobs_and_flags(args[0..-2])
44
+
45
+ [jobs, flags, main, cfg]
46
+ end
47
+
48
+ def try_link(*args)
49
+ link = Pathname.pwd.ascend.map { |p| p.join('.mnogootex.src') }.detect(&:symlink?)
50
+ return if link.nil?
51
+
52
+ main = link.readlink.realpath
53
+ cfg = load_descending(pathname: main.dirname, basename: BASENAME)
54
+ jobs, flags = split_jobs_and_flags(args)
55
+
56
+ [jobs, flags, main, cfg]
57
+ end
58
+
59
+ def try_cfgs(*args)
60
+ yaml = Pathname.pwd.ascend.map { |p| p.join('.mnogootexrc') }.detect(&:file?)
61
+ return if yaml.nil?
62
+
63
+ cfg = load_descending(pathname: yaml.dirname, basename: BASENAME)
64
+ main = yaml.dirname.join(cfg.fetch('main', ''))
65
+ main = main.file? ? main.realpath : nil
66
+ jobs, flags = split_jobs_and_flags(args)
67
+
68
+ [jobs, flags, main, cfg]
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'pathname'
5
+
6
+ require 'mnogootex/utils'
7
+ require 'mnogootex/job/warden'
8
+ require 'mnogootex/job/porter'
9
+ require 'mnogootex/cfg'
10
+
11
+ module Mnogootex
12
+ class CLI < Thor
13
+ desc 'exec [JOB ...] [FLAG ...] ROOT',
14
+ 'Execute latexmk with FLAGs on each JOB for ROOT document'
15
+ def exec(*args)
16
+ execute_latexmk(*args, default_flags: [])
17
+ end
18
+
19
+ desc 'build [JOB ...] [FLAG ...] ROOT',
20
+ 'Build each JOB for ROOT document'
21
+ def build(*args)
22
+ execute_latexmk(*args, default_flags: ['-interaction=nonstopmode'])
23
+ end
24
+
25
+ desc 'open [JOB ...] [FLAG ...] ROOT',
26
+ '(Build and) open the artifact of each JOB for ROOT document'
27
+ def open(*args)
28
+ execute_latexmk(*args, default_flags: ['-interaction=nonstopmode', '-pv'])
29
+ end
30
+
31
+ desc 'clean [JOB ...] [FLAG ...] ROOT',
32
+ 'Delete nonessential files of each JOB for ROOT document'
33
+ def clean(*args)
34
+ execute_latexmk(*args, default_flags: ['-c'])
35
+ end
36
+
37
+ desc 'clobber [JOB ...] [FLAG ...] ROOT',
38
+ 'Delete nonessential files and artifacts of each JOB for ROOT document'
39
+ def clobber(*args)
40
+ execute_latexmk(*args, default_flags: ['-C'])
41
+ end
42
+
43
+ desc 'help [COMMAND]',
44
+ 'Describe available commands or one specific COMMAND'
45
+ def help(*args)
46
+ super
47
+
48
+ puts <<~EXTRA_HELP
49
+ JOBs are document class names. The default is the whole list in your configuration file.
50
+ FLAGs are options passed to latexmk. Please refer to `latexmk -help` for details.
51
+ EXTRA_HELP
52
+ end
53
+
54
+ private
55
+
56
+ def execute_latexmk(*args, default_flags: [])
57
+ jobs, flags, main, cfg = Mnogootex::Cfg.recombobulate(*args)
58
+ cfg = Mnogootex::Cfg::DEFAULTS.merge(cfg).merge({ 'jobs' => jobs }.compact)
59
+ flags = [*default_flags, *flags]
60
+ Mnogootex::Job::Warden.new(source: main, configuration: cfg, flags: flags).start
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'colorize'
4
+
5
+ module Mnogootex
6
+ module Job
7
+ class Logger < Thread
8
+ def initialize(spinner:, processor:, runners:, porters:)
9
+ super do
10
+ while runners.any?(&:alive?)
11
+ self.class.print_status(runners: runners, spinner: spinner)
12
+ sleep 0.02 # 50 fps
13
+ end
14
+ self.class.print_status(runners: runners, spinner: spinner)
15
+ puts
16
+ self.class.print_outcome(runners: runners, porters: porters, processor: processor)
17
+ end
18
+ end
19
+
20
+ class << self
21
+ def print_status(runners:, spinner:)
22
+ spinners_frames = []
23
+ runners.each do |runner|
24
+ spinner_frame = spinner[runner.count_lines % spinner.size]
25
+ spinners_frames << colour_by_state(spinner_frame, runner)
26
+ end
27
+ print "Runners: #{spinners_frames.join}\r"
28
+ end
29
+
30
+ def print_outcome(runners:, porters:, processor:)
31
+ puts 'Outcome:'
32
+ porters.zip(runners).each do |porter, runner|
33
+ outcome_icon = runner.successful? ? '✔'.green : '✘'.red
34
+ puts " #{outcome_icon} #{porter.hid}"
35
+ puts processor.call(runner.log_lines) unless runner.successful?
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def colour_by_state(string, runner)
42
+ if runner.alive?
43
+ string.yellow
44
+ elsif runner.successful?
45
+ string.green
46
+ else
47
+ string.red
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'tmpdir'
5
+
6
+ require 'mnogootex/utils'
7
+
8
+ module Mnogootex
9
+ module Job
10
+ class Porter
11
+ attr_reader :hid
12
+
13
+ def initialize(hid:, source_path:, work_path: nil)
14
+ @source_path = Pathname.new(source_path).realpath
15
+ @work_path = calc_work_path(work_path).tap(&:mkpath).realpath
16
+ @hid = hid
17
+ end
18
+
19
+ def target_dir
20
+ @target_dir ||= @work_path.join(hid)
21
+ end
22
+
23
+ def target_path
24
+ @target_path ||= target_dir.join(@source_path.basename)
25
+ end
26
+
27
+ def clobber
28
+ target_dir.rmtree if target_dir.directory?
29
+ end
30
+
31
+ def provide
32
+ target_dir.mkpath
33
+ providable_files = @source_path.dirname.children
34
+ providable_files.reject!(&@work_path.method(:==))
35
+ FileUtils.cp_r providable_files, target_dir
36
+ remove_configuration(target_dir)
37
+ create_link_to_source(target_dir)
38
+ end
39
+
40
+ private
41
+
42
+ def remove_configuration(folder_path)
43
+ path = folder_path.join('.mnogootexrc')
44
+ path.delete if path.file?
45
+ end
46
+
47
+ def create_link_to_source(folder_path)
48
+ path = folder_path.join('.mnogootex.src')
49
+ path.make_symlink(@source_path) unless path.symlink?
50
+ end
51
+
52
+ def calc_work_path(path)
53
+ return Pathname.new(path) unless path.nil?
54
+
55
+ Pathname.new(Dir.tmpdir).join('mnogootex', source_id)
56
+ end
57
+
58
+ def source_id
59
+ @source_id ||= Utils.short_md5(@source_path.to_s)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'io/wait'
5
+
6
+ module Mnogootex
7
+ module Job
8
+ class Runner
9
+ POLLING_TIMEOUT = 0.02
10
+
11
+ attr_reader :hid, :log_lines
12
+
13
+ def initialize(cmd:, chdir:)
14
+ @log_lines = []
15
+ _, @stream, @thread = Open3.popen2e(*cmd, chdir: chdir)
16
+ @poller = start_poller
17
+ end
18
+
19
+ def alive?
20
+ @poller.alive?
21
+ end
22
+
23
+ def successful?
24
+ @poller.value.exitstatus.zero?
25
+ end
26
+
27
+ def count_lines
28
+ return log_lines.size unless alive?
29
+
30
+ @ticks = [@ticks || -1, log_lines.size - 1].min + 1
31
+ end
32
+
33
+ private
34
+
35
+ def start_poller
36
+ Thread.new do
37
+ polling_loop
38
+
39
+ # NOTE: waits on @thread and returns its value
40
+ @thread.value
41
+ end
42
+ end
43
+
44
+ def polling_loop
45
+ loop do
46
+ if @stream.wait_readable(POLLING_TIMEOUT).nil?
47
+ # If the stream timeouts and the thread is dead we expect no nore data.
48
+ # This happens on commands like `latexmk -pv` which fork other processes.
49
+ break unless @thread.alive?
50
+ else
51
+ # If we reach EOF, we expect no more data.
52
+ break if (line = @stream.gets).nil?
53
+
54
+ log_lines << line
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'colorize'
4
+
5
+ require 'mnogootex/log'
6
+ require 'mnogootex/log/processor'
7
+ require 'mnogootex/job/porter'
8
+ require 'mnogootex/job/runner'
9
+ require 'mnogootex/job/logger'
10
+
11
+ module Mnogootex
12
+ module Job
13
+ class Warden
14
+ LATEXMK_PATH = 'latexmk'
15
+
16
+ def initialize(source:, configuration:, flags:)
17
+ @source = source
18
+ @configuration = configuration
19
+ @flags = flags
20
+
21
+ @processor = nil
22
+ @porters = []
23
+ @runners = []
24
+ @logger = nil
25
+ end
26
+
27
+ def start
28
+ init_processor
29
+ init_porters
30
+ exec_porters
31
+ init_and_exec_runners
32
+ init_and_exec_logger
33
+ @logger.join
34
+ end
35
+
36
+ private
37
+
38
+ def init_porters
39
+ @configuration['jobs'].each do |cls|
40
+ @porters << Mnogootex::Job::Porter.new(
41
+ hid: cls,
42
+ source_path: @source,
43
+ work_path: @configuration['work_path'],
44
+ )
45
+ end
46
+ end
47
+
48
+ def exec_porters
49
+ @porters.each do |porter|
50
+ # porter.clobber
51
+ porter.provide
52
+ transformer(porter.hid, porter.target_path)
53
+ end
54
+ end
55
+
56
+ def init_and_exec_runners
57
+ @runners = @porters.map do |porter|
58
+ Mnogootex::Job::Runner.new(
59
+ cmd: commandline(porter.target_path),
60
+ chdir: porter.target_dir,
61
+ )
62
+ end
63
+ end
64
+
65
+ def init_processor
66
+ @processor = Log::Processor.new(
67
+ matchers: Mnogootex::Log::DEFAULT_MATCHERS,
68
+ levels: Mnogootex::Log::DEFAULT_LEVELS,
69
+ min_level: :info,
70
+ colorize: true,
71
+ indent_width: 4,
72
+ )
73
+ end
74
+
75
+ def init_and_exec_logger
76
+ @logger = Mnogootex::Job::Logger.new(
77
+ spinner: @configuration['spinner'],
78
+ processor: @processor.method(:run),
79
+ runners: @runners,
80
+ porters: @porters,
81
+ )
82
+ end
83
+
84
+ # TODO: generalize, integrate with Runner
85
+ def commandline(target_pathname)
86
+ [
87
+ LATEXMK_PATH,
88
+ *@flags,
89
+ target_pathname.basename.to_s
90
+ ]
91
+ end
92
+
93
+ # TODO: generalize, integrate with Porter
94
+ def transformer(new_class_name, target_pathname)
95
+ old_code = target_pathname.read
96
+ new_code = old_code.sub(
97
+ /\\documentclass(\[.*?\])?{.*?}/,
98
+ "\\documentclass{#{new_class_name}}",
99
+ )
100
+ target_pathname.write(new_code)
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mnogootex
4
+ module Log
5
+ # This data structure represents a log level usually referred to
6
+ # by its {name}. It has a numeric {priority} and a {color} used
7
+ # for rendering.
8
+ #
9
+ # @!attribute priority
10
+ # @return [Numeric] the numeric priority of the log level
11
+ # @!attribute name
12
+ # @return [Symbol] the human readable name of the log level
13
+ # @!attribute color
14
+ # @return [Symbol] the color visually representing the {priority}
15
+ Level = Struct.new(:priority, :name, :color)
16
+ end
17
+ end