mnogootex 0.2.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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