raka 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/lang/r/io.R ADDED
@@ -0,0 +1,113 @@
1
+ suppressPackageStartupMessages(library(RPostgreSQL))
2
+ suppressPackageStartupMessages(library(ggplot2))
3
+ suppressPackageStartupMessages(library(gridExtra))
4
+
5
+ # input
6
+ # -----
7
+ file_input <- function(fileName, coltypes = NA) {
8
+ read.table(fileName, header = T, sep = ',', colClasses = coltypes)
9
+ }
10
+
11
+ join_file_input <- function(namesstr, key) {
12
+ files <- strsplit(namesstr, split = '\\+')[[1]]
13
+ dataList <- lapply(files, (function(f) fileInput(f)))
14
+ do.call(merge, c(dataList, by = key))
15
+ }
16
+
17
+ init_table_input <- function(conn_args, schema) {
18
+ function(name, cols = '*', where = 'true') {
19
+ colstr <- paste(cols, collapse = ',')
20
+
21
+ sql <- sprintf("SELECT %s FROM %s WHERE %s", colstr, name, where)
22
+ init_sql_input(conn_args, schema)(sql)
23
+ }
24
+ }
25
+
26
+ init_sql_input <- function(conn_args, schema) {
27
+ function(sql) {
28
+ conn <- do.call(dbConnect, c(dbDriver('PostgreSQL'), conn_args))
29
+ on.exit(dbDisconnect(conn))
30
+ dbGetQuery(conn, paste('SET search_path TO', paste(c(schema, 'public'), sep = ',')))
31
+ buffer <- dbSendQuery(conn, sql)
32
+ fetch(buffer, n=-1)
33
+ }
34
+ }
35
+
36
+ create_parents <- function(output) {
37
+ dir.create(dirname(output), showWarnings = FALSE, recursive = TRUE)
38
+ }
39
+
40
+ # Output
41
+ # ------
42
+ # TODO consider deprecating this, perhaps more specific ones like graph_output is still useful
43
+ auto_output <- function(report, ...) {
44
+ actual <- switch(class(report)[1],
45
+ gg = ggplot_output,
46
+ data.frame = csv_output,
47
+ print)
48
+ actual(report, ...)
49
+ }
50
+
51
+ rplot_output <- function(f, output, size = c(8, 8)) {
52
+ create_parents(output)
53
+ size <- ceiling(size / 2.54 * 300 / 96) # convert cm to inch
54
+ par(mar = c(0,0,0,0))
55
+ # use pdf since png can be very weird, e.g. radarplot
56
+ pdf(output, width = size[1], height = size[2], pointsize = 12)
57
+ f()
58
+ dev.off()
59
+ }
60
+
61
+ # use facet or latex subfigure, do not handle list anymore
62
+ ggplot_output <- function(report, output, size = c(15, 15), fontScale = 1, compact = F) {
63
+ create_parents(output)
64
+ if (compact) {
65
+ margin <- 1
66
+ } else {
67
+ margin <- 5
68
+ }
69
+
70
+ report <- report + theme_bw() +
71
+ theme(axis.title = element_text(size = 13 * fontScale),
72
+ axis.text = element_text(size = 11 * fontScale),
73
+ legend.title = element_text(size = 13 * fontScale),
74
+ legend.text = element_text(size = 11 * fontScale),
75
+ legend.key.size = unit(13 * fontScale, 'pt'),
76
+ legend.margin = unit(0, "cm"),
77
+ legend.background = element_rect(fill = alpha('white', 0)),
78
+ plot.margin = unit(c(margin, margin, 0, 0),"mm"))
79
+
80
+ width <- size[1]
81
+ height <- size[2]
82
+
83
+ ggsave(report, filename = output,
84
+ dpi = 300, units = 'cm', width = width, height = height, limitsize = FALSE)
85
+ }
86
+
87
+ csv_output <- function(report, output) {
88
+ create_parents(output)
89
+ write.table(format(report, digits = 4), file = toString(output), row.names = FALSE,
90
+ sep = ',', quote = FALSE)
91
+ }
92
+
93
+ txt_output <- function(report, output) {
94
+ create_parents(output)
95
+ write.table(format(report, digits = 4), file = toString(output), row.names = FALSE,
96
+ sep = ',', quote = FALSE)
97
+ }
98
+
99
+ init_table_output <- function(conn_args, schema) {
100
+ function(report, output, placeholder = NULL) {
101
+ conn <- do.call(dbConnect, c(dbDriver('PostgreSQL'), conn_args))
102
+ on.exit(dbDisconnect(conn))
103
+ dbGetQuery(conn, paste('SET search_path TO', paste(c(schema, 'public'), sep = ',')))
104
+
105
+ if (dbExistsTable(conn, output)) {
106
+ dbRemoveTable(conn, output)
107
+ }
108
+ dbWriteTable(conn, output, report, row.names = F)
109
+ if (!is.null(placeholder)) {
110
+ write.table(data.frame(), file = placeholder, col.names = FALSE)
111
+ }
112
+ }
113
+ }
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../protocol'
4
+
5
+ # shell(bash) protocol
6
+ class Shell
7
+ # @implements LanguageImpl
8
+ def build(code, _)
9
+ ['set -e', code].join "\n"
10
+ end
11
+
12
+ def run_script(env, fname, _)
13
+ run_cmd(env, "bash #{fname}")
14
+ end
15
+ end
16
+
17
+ creator :shell, Shell
@@ -0,0 +1,36 @@
1
+ class OutputType
2
+ class Default
3
+ # File name is for checking the existence of outputs
4
+ def file_name(rule_name, scope = '')
5
+ scope.to_s + (scope.empty? ? '' : '/') + rule_name
6
+ end
7
+
8
+ # Real name is for further processing, like schema.table_name in database
9
+ # defaultly the file is the output, but when the file is merely a placeholder
10
+ # the two will differ
11
+ def real_name(rule_name, scope = '')
12
+ file_name
13
+ end
14
+
15
+ def ext
16
+ res = self.class.to_s
17
+ res[0] = res[0].downcase
18
+ res
19
+ end
20
+ end
21
+
22
+ class Table < Default
23
+ def real_name(file_name, scope = '')
24
+ stem = file_name.gsub(/\.table$/, '')
25
+ scope.to_s + (scope.empty? ? '' : '.') + stem
26
+ end
27
+ end
28
+
29
+ def create(sym)
30
+ self.class.const_get(sym.capitalize).new || Default.new
31
+ end
32
+
33
+ def self.parse_option(opt)
34
+ opt.map { |obj| obj.class == Symbol ? create(obj) : obj }
35
+ end
36
+ end
data/lib/protocol.rb ADDED
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'open3'
5
+
6
+ def remove_common_indent(code)
7
+ code.gsub(/^#{code.scan(/^[ \t]+(?=\S)/).min}/, '')
8
+ end
9
+
10
+ def create_tmp(content)
11
+ tmpfile = "/tmp/#{SecureRandom.uuid}"
12
+
13
+ File.open(tmpfile, 'w') do |f|
14
+ f.write content
15
+ end
16
+
17
+ tmpfile
18
+ end
19
+
20
+ # protocol conforms the interface:
21
+ #
22
+ # call(env, task) resolve
23
+ #
24
+ # call :: rake's main -> dsl task(see compiler) -> void
25
+ # resolve :: str -> str
26
+
27
+ # There are two methods to provide code to a language protocol, either a string literal
28
+ # OR a ruby block. Cannot choose both.
29
+ class LanguageProtocol
30
+ attr_writer :block
31
+
32
+ private
33
+
34
+ def wrap_template(code)
35
+ @script_template.gsub(/^\<code\>$/, code)
36
+ end
37
+
38
+ public
39
+
40
+ def initialize(language_impl, script_template: '<code>')
41
+ # contextual variables, will be passed later
42
+ @impl = language_impl
43
+ @script_template = script_template
44
+ @block = nil
45
+ @text = nil
46
+ end
47
+
48
+ # for syntax sugar like shell* <code text>
49
+ def *(text)
50
+ @text = text
51
+ [self]
52
+ end
53
+
54
+ # a block::str -> str should be given to resolve the bindings in code text
55
+ def call(env, task)
56
+ code = yield @text if @text
57
+ code = @block.call(task) if @block # do not resolve
58
+
59
+ env.logger.debug code
60
+ script_text = @impl.build(wrap_template(remove_common_indent(code)), task)
61
+ temp_script = create_tmp(script_text)
62
+ @impl.run_script env, temp_script, task
63
+ env.logger.debug script_text
64
+ end
65
+ end
66
+
67
+ # A special language protocol, just a wrapper for action, pass block instead of
68
+ # string to execute
69
+ # named RubyP to avoid name collision
70
+ class RubyP
71
+ def initialize(&block)
72
+ @block = block
73
+ end
74
+
75
+ def call(_, task, &resolve)
76
+ @block.call(task, &resolve)
77
+ FileUtils.touch(task.name)
78
+ end
79
+ end
80
+
81
+ # use run instead of "ruby" to avoid name collision
82
+ def run(&block)
83
+ [RubyP.new(&block)]
84
+ end
85
+
86
+ # helper functions to implement LanguageImpl
87
+ def run_cmd(env, cmd)
88
+ env.logger.debug(cmd)
89
+ Open3.popen3(cmd) do |_stdin, stdout, stderr, _thread|
90
+ env.logger.debug(stdout.read)
91
+ err = stderr.read
92
+ env.logger.info(err) unless err.empty?
93
+ end
94
+ end
95
+
96
+ def pick_kwargs(klass, kwargs)
97
+ param_ref = klass.instance_method(:initialize).parameters
98
+ .select { |arg| arg.size == 2 && (arg[0] == :key || arg[0] == :keyreq) }
99
+ .map { |arg| arg[1] }
100
+ kwargs.select do |key, _value|
101
+ param_ref.include? key
102
+ end
103
+ end
104
+
105
+ def creator(name, klass, global_defaults = {})
106
+ global_config = global_defaults
107
+ define_singleton_method name do |*args, **kwargs, &block|
108
+ # pick keyword arguments for klass
109
+ kwargs = global_config.merge kwargs
110
+ impl = klass.new(*args, **pick_kwargs(klass, kwargs))
111
+ proto = LanguageProtocol.new(impl, **pick_kwargs(LanguageProtocol, kwargs))
112
+ if block
113
+ proto.block = block
114
+ [proto]
115
+ else
116
+ proto.define_singleton_method :config do |**config|
117
+ global_config = global_defaults.merge config
118
+ end
119
+ proto # if no block, allow configure or waiting for * to add code text
120
+ end
121
+ end
122
+ end
data/lib/raka.rb ADDED
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ require_relative './compile'
6
+ require_relative './protocol'
7
+ require_relative './token'
8
+
9
+ # initialize raka
10
+ class Raka
11
+ Pattern = Pattern
12
+ attr_reader :logger
13
+
14
+ def create_logger(level)
15
+ @env.define_singleton_method :logger do
16
+ logger = Logger.new(STDOUT)
17
+ logger.level = level
18
+ logger
19
+ end
20
+ end
21
+
22
+ def define_token_creator(ext, ext_alias = nil)
23
+ # closures
24
+ env = @env
25
+ options = @options
26
+ scopes = @scopes
27
+ @env.define_singleton_method(ext_alias || ext) do |*args|
28
+ # Here the compiler are bound with @options so that when we change @options
29
+ # using methods like scope in Rakefile, the subsequent rules defined will honor
30
+ # the new settings
31
+ # clone to fix the scopes when defining rule
32
+ inline_scope_pattern = !args.empty? ? args[0] : nil
33
+ Token.new(
34
+ DSLCompiler.new(env, options), Context.new(ext, scopes.clone),
35
+ [], inline_scope_pattern
36
+ )
37
+ end
38
+ end
39
+
40
+ def initialize(env, options)
41
+ @env = env
42
+ defaults = {
43
+ output_types: [:csv], input_types: [],
44
+ type_aliases: {},
45
+ scopes: [],
46
+ lang: ['lang/shell'],
47
+ user_lang: []
48
+ }
49
+ @options = options = OpenStruct.new(defaults.merge(options))
50
+
51
+ create_logger options.log_level || (ENV['LOG_LEVEL'] || Logger::INFO).to_i
52
+
53
+ @options.input_types |= @options.output_types # any output can be used as intermediate
54
+ # specify root of scopes in options, scopes will append to each root
55
+ @scopes = options.scopes.empty? ? [] : [options.scopes]
56
+ @options.lang.each { |path| load File::join(File::dirname(__FILE__), "#{path}/impl.rb") }
57
+ @options.user_lang.each { |path| load path.to_s + '.rb' }
58
+
59
+ # These are where the dsl starts
60
+ @options.output_types.each do |ext|
61
+ define_token_creator(ext, @options.type_aliases[ext])
62
+ end
63
+ end
64
+
65
+ def scope(*names, &block)
66
+ @scopes.push(names)
67
+ block.call
68
+ @scopes.pop
69
+ end
70
+ end