Class: DSLCompiler

Inherits:
Object
  • Object
show all
Defined in:
lib/raka/compile.rb

Overview

compiles rule (lhs = rhs) to rake task

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(env, options) ⇒ DSLCompiler

keep env as running environment of rake since we want to inject rules



23
24
25
26
# File 'lib/raka/compile.rb', line 23

def initialize(env, options)
  @env = env
  @options = options
end

Instance Attribute Details

#envObject (readonly)

Returns the value of attribute env.



20
21
22
# File 'lib/raka/compile.rb', line 20

def env
  @env
end

Instance Method Details

#compile(lhs, rhs) ⇒ Object

compile token = rhs to rake rule rubocop:disable Style/MethodLength rubocop:disable Style/PerceivedComplexity



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
# File 'lib/raka/compile.rb', line 135

def compile(lhs, rhs)
  unless @env.instance_of?(Object)
    raise "DSL compile error: seems not a valid @env of rake with class #{@env.class}"
  end

  # the format is [dep, ...] | [action, ...] | [post, ...], where the posts
  # are those will be raked after the actions
  actions_start = rhs.find_index { |item| item.respond_to?(:call) }

  # case 1: has action
  if actions_start
    extra_deps = rhs[0, actions_start]
    actions_end = rhs[actions_start, rhs.length].find_index do |item|
      !item.respond_to?(:call)
    end

    # case 1.1: has post
    if actions_end
      actions_end += actions_start
      actions = rhs[actions_start, actions_end]
      extra_tasks = rhs[actions_end, rhs.length]
    # case 1.2: no post
    else
      actions = rhs[actions_start, rhs.length]
      extra_tasks = []
    end
  # case 2: no action
  else
    extra_deps = rhs
    actions = []
    extra_tasks = []
  end

  unless lhs._input_?
    create_rule lhs, proc { [] }, actions, extra_deps, extra_tasks
    return
  end

  # We generate a rule for each possible input type

  (lhs._options_[:input_exts] || @options.input_types).each do |ext|
    # We find auto source from both THE scope and the root
    create_rule lhs, ext, actions, extra_deps, extra_tasks
  end
end

#create_rule(lhs, input_ext, actions, extra_deps, extra_tasks) ⇒ Object

build one rule



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/raka/compile.rb', line 110

def create_rule(lhs, input_ext, actions, extra_deps, extra_tasks)
  # the "rule" method is private, maybe here are better choices
  @env.send(:rule, lhs._pattern_ => [proc do |target|
    inputs = lhs._inputs_(target, input_ext)
    output = lhs._parse_output_(target)
    plain_extra_deps = extra_deps.map do |templ|
      resolve_by_output(templ, output)
    end
    # main data source and extra dependencies
    inputs + plain_extra_deps
  end]) do |task|
    # rake continue task even if dependencies not met, we handle ourselves
    absence = task.prerequisites.find_index { |f| !File.exist? f }
    unless absence.nil?
      @env.logger.warn\
        "Dependent #{task.prerequisites[absence]} does not exist, skip task #{task.name}"
      next
    end
    rule_action(lhs, actions, extra_tasks, task)
  end
end

#dsl_task(token, task) ⇒ Object

Raka task structure, input task is rake’s task pushed into blocks



29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/raka/compile.rb', line 29

def dsl_task(token, task)
  name = task.name
  deps = task.prerequisites 
  output_info = token._parse_output_ name
  task_info = {
    name: name,
    deps: deps,
    deps_str: deps.join(','),
    input: deps.first || '',
    task: task
  }
  OpenStruct.new(output_info.to_h.merge(task_info))
end

#resolve(target, task) ⇒ Object

resolve auto variables with dsl task



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/raka/compile.rb', line 70

def resolve(target, task)
  # convert target to text whether it is expression or already text
  text = resolve_by_output target, task

  # convert $0, $1 to the universal shape of %{dep} as captures
  text = text
    .gsub('$^', task.deps_str)
    .gsub('$<', task.input || '')
    .gsub('$(deps)', task.deps_str)
    .gsub('$(input)', task.input || '')

  protect_percent_symbol text do |safe_text|
    # add numbered auto variables like $0, $2 referring to the first and third deps
    safe_text = safe_text.gsub(/\$\(dep(\d+)\)/, '%{\1}') % array_to_hash(task.deps)
    safe_text.gsub(/\$\(dep(\d+)_stem\)/, '%{\1}') % array_to_hash(task.deps.map {|d| stem(d)})
  end
end

#resolve_by_output(target, output_info) ⇒ Object

resolve auto variables with only output info, useful when resolve extra deps (task is not available yet)



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/raka/compile.rb', line 49

def resolve_by_output(target, output_info)
  info = output_info
  text = target.respond_to?(:_template_) ? target._template_(info.scope).to_s : target.to_s
  text = text
    .gsub('$(scope)', info.scope.nil? ? '' : info.scope)
    .gsub('$(target_scope)', info.target_scope.nil? ? '' : info.target_scope)
    .gsub('$(output)', info.output)
    .gsub('$(output_stem)', stem(info.stem))
    .gsub('$(input_stem)', info.input_stem.nil? ? '' : info.input_stem)
    .gsub('$(func)', info.func.nil? ? '' : info.func)
    .gsub('$(ext)', info.ext)
    .gsub('$@', info.name)

  protect_percent_symbol text do |safe_text|
    safe_text = safe_text % (info.to_h.merge info.captures.to_h)
    safe_text = safe_text.gsub(/\$\(rule_scope(\d+)\)/, '%{\1}') % array_to_hash(info.rule_scopes)
    safe_text.gsub(/\$\(target_scope(\d+)\)/, '%{\1}') % array_to_hash(info.target_scope_captures)
  end
end

#rule_action(lhs, actions, extra_tasks, task) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/raka/compile.rb', line 88

def rule_action(lhs, actions, extra_tasks, task)
  return if actions.empty?

  task = dsl_task(lhs, task)
  @env.logger.info "raking #{task.name}"
  unless task.scope.nil?
    folder = task.scope
    folder = File.join(task.scope, task.target_scope) unless task.target_scope.nil?
    FileUtils.makedirs(folder)
  end
  actions.each do |action|
    action.call @env, task do |code|
      resolve(code, task)
    end
  end

  extra_tasks.each do |templ|
    Rake::Task[resolve(templ, task)].invoke
  end
end

#stem(path) ⇒ Object



43
44
45
# File 'lib/raka/compile.rb', line 43

def stem(path)
  File.basename(path, File.extname(path))
end