aboutsummaryrefslogtreecommitdiff
path: root/libbutl/builtin.mxx
blob: e4dd4f81e63dd99fa8491c214194d0c9a321947a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// file      : libbutl/builtin.mxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#ifndef __cpp_modules_ts
#pragma once
#endif

// C includes.

#ifndef __cpp_lib_modules_ts
#include <map>
#include <string>
#include <vector>
#include <thread>
#include <cstddef>    // size_t
#include <utility>    // move()
#include <cstdint>    // uint8_t
#include <functional>
#endif

// Other includes.

#ifdef __cpp_modules_ts
export module butl.builtin;
#ifdef __cpp_lib_modules_ts
import std.core;
import std.threading;
#endif
import butl.path;
import butl.fdstream;
import butl.timestamp;
#else
#include <libbutl/path.mxx>
#include <libbutl/fdstream.mxx>
#include <libbutl/timestamp.mxx>
#endif

#include <libbutl/export.hxx>

LIBBUTL_MODEXPORT namespace butl
{
  // A process/thread-like object representing a running builtin.
  //
  // For now, instead of allocating the result storage dynamically, we
  // expect it to be provided by the caller.
  //
  class builtin
  {
  public:
    std::uint8_t
    wait () {if (t_.joinable ()) t_.join (); return r_;}

    ~builtin () {wait ();}

  public:
    builtin (std::uint8_t& r, std::thread&& t = std::thread ())
        : r_ (r), t_ (move (t)) {}

    builtin (builtin&&) = default;

  private:
    std::uint8_t& r_;
    std::thread t_;
  };

  // Builtin execution callbacks that can be used for checking/handling the
  // filesystem entries being acted upon (enforcing that they are sub-entries
  // of some "working" directory, registering cleanups for new entries, etc)
  // and for providing custom implementations for some functions used by
  // builtins.
  //
  // Note that the filesystem paths passed to the callbacks are absolute and
  // normalized with directories distinguished from non-directories based on
  // the lexical representation (presence of the trailing directory separator;
  // use path::to_directory() to check).
  //
  // Also note that builtins catch any exceptions that may be thrown by the
  // callbacks and, if that's the case, issue diagnostics and exit with the
  // non-zero status.
  //
  struct builtin_callbacks
  {
    // If specified, called before (pre is true) and after (pre is false) a
    // new filesystem entry is created or an existing one is re-created or
    // updated.
    //
    using create_hook = void (const path&, bool pre);

    std::function<create_hook> create;

    // If specified, called before (pre is true) and after (pre is false) a
    // filesystem entry is moved. The force argument is true if the builtin is
    // executed with the --force option.
    //
    using move_hook = void (const path& from,
                            const path& to,
                            bool force,
                            bool pre);

    std::function<move_hook> move;

    // If specified, called before (pre is true) and after (pre is false) a
    // filesystem entry is removed. The force argument is true if the builtin
    // is executed with the --force option.
    //
    using remove_hook = void (const path&, bool force, bool pre);

    std::function<remove_hook> remove;

    // If specified, called on encountering an unknown option passing the
    // argument list and the position of the option in question. Return the
    // number of parsed arguments.
    //
    using parse_option_function =
      std::size_t (const std::vector<std::string>&, std::size_t);

    std::function<parse_option_function> parse_option;

    // If specified, called by the sleep builtin instead of the default
    // implementation.
    //
    using sleep_function = void (const duration&);

    std::function<sleep_function> sleep;

    explicit
    builtin_callbacks (std::function<create_hook>           c = {},
                       std::function<move_hook>             m = {},
                       std::function<remove_hook>           r = {},
                       std::function<parse_option_function> p = {},
                       std::function<sleep_function>        s = {})
        : create (std::move (c)),
          move (std::move (m)),
          remove (std::move (r)),
          parse_option (std::move (p)),
          sleep (std::move (s)) {}

    explicit
    builtin_callbacks (std::function<sleep_function> sl)
        : sleep (std::move (sl)) {}
  };

  // Start a builtin command. Use the current process' standard streams for
  // the unopened in, out, and err file descriptors. Use the process' current
  // working directory unless an alternative is specified. Throw
  // std::system_error on failure.
  //
  // Note that unlike argc/argv, args don't include the program name.
  //
  using builtin_function = builtin (std::uint8_t& result,
                                    const std::vector<std::string>& args,
                                    auto_fd in, auto_fd out, auto_fd err,
                                    const dir_path& cwd,
                                    const builtin_callbacks&);

  // Builtin function and weight.
  //
  // The weight between 0 and 2 reflects the builtin's contribution to the
  // containing script semantics with 0 being the lowest/ignore. Current
  // mapping is as follows:
  //
  // 0 - non-contributing (true, false)
  // 1 - non-creative     (rm, rmdir, sleep, test)
  // 2 - creative         (any builtin that may produce output)
  //
  // If the function is NULL, then the builtin has an external implementation
  // and should be executed by running the program with this name.
  //
  struct builtin_info
  {
    builtin_function* function;
    uint8_t           weight;
  };

  class builtin_map: public std::map<std::string, builtin_info>
  {
  public:
    using base = std::map<std::string, builtin_info>;
    using base::base;

    // Return NULL if not a builtin.
    //
    const builtin_info*
    find (const std::string& n) const
    {
      auto i (base::find (n));
      return i != end () ? &i->second : nullptr;
    }
  };

  LIBBUTL_SYMEXPORT extern const builtin_map builtins;
}