xing-root 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f1e5c25fdf0a0131b66f61a48b065e7a042edfda
4
+ data.tar.gz: bdda3cd893b79191835a14ade4ce89b6a63844d5
5
+ SHA512:
6
+ metadata.gz: e3e77cdf6e85e6108aa2876c879a783e68a97ffc64d3ab4c525d43ce6fb9cf369133f42e01d93dd05b91f12fe954a37a5b396e5365ed86769f1456dad9084467
7
+ data.tar.gz: 57bb2b0db2a4884c8eef59f885d21241b842e15571a3f34484e25c721be0ca8240824e07e6da660da7400921ae5f76cdd06aee86ae869682c8ef363da489a514
@@ -0,0 +1,16 @@
1
+ require 'edict'
2
+ require 'xing/edicts/clean-run'
3
+
4
+ module Xing
5
+ module Edicts
6
+ class CleanRake < CleanRun
7
+
8
+ setting :task_name
9
+
10
+ def setup
11
+ super
12
+ self.shell_cmd = %w[bundle exec rake] + [task_name]
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,40 @@
1
+ require 'caliph'
2
+ require 'edict'
3
+
4
+ module Xing
5
+ module Edicts
6
+ class CleanRun < Edict::Rule
7
+ include Caliph::CommandLineDSL
8
+
9
+ setting :dir
10
+ setting :shell_cmd
11
+ setting :env_hash
12
+ setting :caliph_shell
13
+
14
+ def setup_defaults
15
+ super
16
+ @caliph_shell = Caliph::Shell.new
17
+ end
18
+
19
+ def setup
20
+ self.env_hash ||= {}
21
+ end
22
+
23
+ def action
24
+ Bundler.with_clean_env do
25
+ Dir.chdir(dir) do
26
+ command = cmd(*shell_cmd)
27
+
28
+ env_hash.each_pair do |name, value|
29
+ command.set_env(name, value)
30
+ end
31
+
32
+ result = caliph_shell.run(command)
33
+
34
+ result.must_succeed!
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,91 @@
1
+ require 'caliph'
2
+ require 'edict'
3
+ require 'xing-root'
4
+
5
+ module Xing::Edicts
6
+ class LaunchBrowser < Edict::Command
7
+ setting :reload_server_port
8
+ setting :static_server_port
9
+ setting :setup_time_limit, 60
10
+ setting :max_wait, 16
11
+
12
+ def initialize
13
+ self.command = ""
14
+ super
15
+ end
16
+
17
+ def check_live_reload_server!
18
+ begin_time = Time.now
19
+ begin
20
+ test_conn = TCPSocket.new 'localhost', reload_server_port
21
+ rescue Errno::ECONNREFUSED
22
+ if Time.now - begin_time > setup_time_limit
23
+ puts "Couldn't connect to test server on localhost:#{reload_server_port} after #{setup_time_limit} seconds - bailing out"
24
+ exit 1
25
+ else
26
+ sleep 0.05
27
+ retry
28
+ end
29
+ ensure
30
+ test_conn.close rescue nil
31
+ end
32
+ end
33
+
34
+ def check_existing_browser
35
+ started = Time.now
36
+ changes = {}
37
+ while(Time.now - started < max_wait)
38
+ begin
39
+ changes = JSON.parse(Net::HTTP.get(URI("http://localhost:#{reload_server_port}/changed")))
40
+ rescue Errno::ECONNREFUSED
41
+ puts "LiveReload server abruptly stopped receiving connections. Bailing out."
42
+ exit 2
43
+ end
44
+
45
+ if changes["clients"].empty?
46
+ sleep 0.25
47
+ else
48
+ break
49
+ end
50
+ end
51
+ changes["clients"]
52
+ end
53
+
54
+ def launch_new_browser
55
+ puts
56
+ puts "No running development browsers: launching...."
57
+
58
+ browser_command = %w{open xdg-open chrome chromium}.find do |command|
59
+ run_command(cmd('which', command)).succeeds?
60
+ end
61
+
62
+ if browser_command.nil?
63
+ raise "Can't find any executable to launch a browser with."
64
+ end
65
+
66
+ run_command( cmd(browser_command, "http://localhost:#{static_server_port}/") ).must_succeed!
67
+ end
68
+
69
+ def subprocess_action
70
+ require 'net/http'
71
+ require 'json'
72
+
73
+ check_live_reload_server!
74
+
75
+ existing = check_existing_browser
76
+
77
+ if existing.empty?
78
+ launch_new_browser
79
+ else
80
+ puts
81
+ puts "There's already a browser attached to the LiveReload server."
82
+ end
83
+ end
84
+
85
+ def action
86
+ fork do
87
+ subprocess_action
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,11 @@
1
+ module Xing::Edicts
2
+ class StartChild < Edict::Rule
3
+ setting :manager
4
+ setting :label
5
+ setting :child_task
6
+
7
+ def action
8
+ manager.start_child(label, child_task)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,99 @@
1
+ require 'xing-root'
2
+ require 'find'
3
+ require 'edict'
4
+
5
+ module Xing::Edicts
6
+ class StructureChecker < Edict::Rule
7
+ include Find
8
+
9
+ class Error < ::StandardError
10
+ end
11
+
12
+ setting :dir
13
+ nil_field :context_hash
14
+ setting :out_stream, $stdout
15
+
16
+ def setup
17
+ @problems = []
18
+ end
19
+
20
+ def action
21
+ analyze
22
+ report
23
+ end
24
+
25
+ def analyze
26
+ context = context_from_hash(context_hash || {:escapes => %w{common framework build}})
27
+ return unless File.directory?(dir)
28
+ find(dir) do |path|
29
+ if File.directory?(path)
30
+ else
31
+ next if File.basename(path)[0] == ?.
32
+ case path
33
+ when /\.js\z/
34
+ check_imports(path, context)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ def context_from_hash(hash)
41
+ Context.new(hash)
42
+ end
43
+
44
+ def report
45
+ unless @problems.empty?
46
+ @problems.group_by do |problem|
47
+ problem.file
48
+ end.each do |file, problems|
49
+ out_stream.puts "In #{file}"
50
+ problems.each{|prob| out_stream.puts " " + prob.to_s}
51
+ out_stream.puts
52
+ end
53
+ raise Error, "Problems found in ECMAScript structure"
54
+ end
55
+ end
56
+
57
+ class Context
58
+ def initialize(hash)
59
+ @escape_clause_list = hash.delete(:escapes) || %w{common framework build}
60
+ raise "Unknown fields in context: #{hash.inspect}" unless hash.empty?
61
+ end
62
+
63
+ attr_reader :escape_clause_list
64
+ end
65
+
66
+ class Problem < ::Struct.new(:msg, :line, :lineno, :file)
67
+ def to_s
68
+ "#{lineno}:<#{line}>: #{msg}"
69
+ end
70
+ end
71
+
72
+ def problem(msg, line, lineno, file)
73
+ @problems << Problem.new(msg, line.chomp, lineno + 1, file)
74
+ end
75
+
76
+ def check_imports(path, context)
77
+ File.read(path).lines.grep(/\s*import/).each_with_index do |import_line, lineno|
78
+ md = /.*from (?<quote>['"])(?<from>.*)\k<quote>/.match(import_line)
79
+ if md.nil?
80
+ problem "doesn't seem to have a 'from' clause...", import_line, lineno, path
81
+ end
82
+
83
+ if /\.\./ =~ md[:from]
84
+ if /\A\.\./ !~ md[:from]
85
+ problem "from includes .. not at pos 0", import_line, lineno, path
86
+ end
87
+ if /\w.*\.\./ =~ md[:from]
88
+ problem "from includes .. after words", import_line, lineno, path
89
+ end
90
+ if !(violation = %r{(?<dir>\w+)/\w}.match md[:from]).nil?
91
+ unless %r{\.\./(#{context.escape_clause_list.join("|")})} =~ md[:from]
92
+ problem "Imports Rule: 'from' includes ../ and then #{violation[:dir].inspect} not in #{context.escape_clause_list.inspect}", import_line, lineno, path
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,3 @@
1
+ require 'xing/edicts/clean-run'
2
+ require 'xing/edicts/start-child'
3
+ require 'xing/edicts/launch-browser'
@@ -0,0 +1,110 @@
1
+ class ChildManager
2
+ def initialize
3
+ @child_pids = []
4
+ @parent_pid = Process.pid
5
+ @child_data = {}
6
+ end
7
+ attr_accessor :child_pids, :child_data
8
+
9
+ ChildRecord = Struct.new(:name, :status)
10
+
11
+ def start_child(name, task)
12
+ child_pid = Process.fork do
13
+ Signal.trap("HUP") do
14
+ puts "Parent exited"
15
+ exit
16
+ end
17
+
18
+ exec(*(%w{bundle exec rake} + [task]))
19
+ end
20
+ puts "#{@parent_pid}: #{name} running in pid #{child_pid}"
21
+
22
+ at_exit { kill_child(child_pid) }
23
+ child_data[child_pid] = ChildRecord.new(name, nil)
24
+ child_pids << child_pid
25
+ end
26
+
27
+ def exited?(pid)
28
+ return false unless child_data.has_key?(pid)
29
+ return true unless child_data[pid].status.nil?
30
+
31
+ begin
32
+ _apid, status = *Process.wait2(pid, Process::WNOHANG | Process::WUNTRACED)
33
+ rescue Errno::ECHILD
34
+ return false
35
+ end
36
+
37
+ unless status.nil?
38
+ child_data[pid].status = status
39
+ return true
40
+ end
41
+ return false
42
+ end
43
+
44
+ def wait_until_dead(pilimit)
45
+ start = Time.now
46
+ while Time.now - start < limit
47
+ Process.waitpid(-1, Process::WNOHANG | Process::WUNTRACED)
48
+ sleep(0.1)
49
+ end
50
+ return false
51
+ rescue SystemCallError # = no children
52
+ return true
53
+ end
54
+
55
+ def wait_all
56
+ Process.waitall
57
+ end
58
+
59
+ def kill_child(pid)
60
+ unless Process.pid == @parent_pid
61
+ puts "#{Process.pid} #@parent_pid Not original parent: not killing"
62
+ return
63
+ end
64
+ puts "PID #{Process.pid} is killing child #{pid} #{child_data[pid].name}"
65
+
66
+ if exited?(pid)
67
+ puts "#{pid} #{child_data[pid].name} already exited"
68
+ return
69
+ end
70
+
71
+ begin
72
+ Process::kill("TERM", pid)
73
+ rescue Errno::ESRCH
74
+ puts "Hm. #{pid} is already gone: dead?"
75
+ return
76
+ end
77
+
78
+ limit = 10
79
+ start = Time.now
80
+ while Time.now - start < limit
81
+ begin
82
+ Process::kill(0, pid)
83
+ sleep(0.1)
84
+ rescue Errno::ESRCH
85
+ return
86
+ end
87
+ end
88
+
89
+ begin
90
+ Process::kill("KILL", pid)
91
+ rescue Errno::ESRCH
92
+ return
93
+ end
94
+ end
95
+
96
+ def kill_all
97
+ unless Process.pid == @parent_pid
98
+ puts "Although #{Process.pid} was asked to kill all, it isn't #@parent_pid the original parent"
99
+ return
100
+ end
101
+ child_pids.each do |pid|
102
+ kill_child(pid)
103
+ end
104
+ previous_term_trap = trap "TERM" do
105
+ puts "Trapped TERM once"
106
+ trap "TERM", previous_term_trap
107
+ end
108
+ Process::kill("TERM", 0)
109
+ end
110
+ end
@@ -0,0 +1,188 @@
1
+ require 'caliph'
2
+
3
+ module Xing
4
+ module Managers
5
+ class Tmux
6
+ include Caliph::CommandLineDSL
7
+
8
+ MINIMUM_WINDOW_COLUMNS = 75
9
+ MINIMUM_WINDOW_LINES = 18
10
+
11
+ def initialize(shell=nil)
12
+ @shell = shell || Tmux.shell
13
+ @first_child = true
14
+ @extra_config_path='~/.lrd-dev-tmux.conf'
15
+ end
16
+ attr_reader :shell
17
+
18
+ class << self
19
+ def shell
20
+ @shell ||= Caliph.new
21
+ end
22
+
23
+ def available?
24
+ shell.run("which", "tmux").succeeds?
25
+ end
26
+ end
27
+
28
+ def tmux_exe
29
+ @tmux_exe ||= shell.run("which", "tmux").stdout.chomp
30
+ end
31
+
32
+ def tmux(*cmd)
33
+ command = cmd(tmux_exe, *cmd)
34
+ puts command.string_format
35
+ shell.run(command).stdout.chomp
36
+ end
37
+
38
+ def copied_env_vars
39
+ %w{PORT_OFFSET GEM_HOME GEM_PATH}
40
+ end
41
+
42
+ def default_env
43
+ {"PORT_OFFSET" => 0}
44
+ end
45
+
46
+ def session_env
47
+ Hash[copied_env_vars.map do |varname|
48
+ varvalue = ENV[varname] || default_env[varname]
49
+ [varname, varvalue] unless varvalue.nil?
50
+ end.compact]
51
+ end
52
+
53
+ def env_string
54
+ session_env.map do |envpair|
55
+ envpair.join("=")
56
+ end.join(" ")
57
+ end
58
+
59
+ def rake_command(task)
60
+ "env #{env_string} bundle exec rake #{task}"
61
+ end
62
+
63
+ def wait_all
64
+ path = File.expand_path(@extra_config_path)
65
+ if File.exists?(path)
66
+ puts "Loading #{path}"
67
+ tmux "source-file #{path}"
68
+ else
69
+ puts "No extra config found at #{path}"
70
+ end
71
+
72
+ tmux "attach-session -d" unless existing?
73
+ end
74
+
75
+ def existing?
76
+ !(ENV['TMUX'].nil? or ENV['TMUX'].empty?)
77
+ end
78
+
79
+ end
80
+
81
+ class TmuxPane < Tmux
82
+
83
+ def initialize(shell=nil)
84
+ super
85
+ @window_name = "Dev Servers"
86
+ @pane_count = 0
87
+ @window_count = 1
88
+ end
89
+ attr_accessor :window_name
90
+
91
+ def new_window_after
92
+ @new_window_after ||= calculate_lines_per_window
93
+ end
94
+
95
+ # need to use %x here rather than open a Caliph subshell
96
+ # won't ahve the same terminal with subshell
97
+ def tput_lines
98
+ %x"tput lines".chomp.to_i
99
+ end
100
+
101
+ def tput_cols
102
+ %x"tput cols".chomp.to_i
103
+ end
104
+
105
+ def calculate_lines_per_window
106
+ lines = tput_lines
107
+ min_lines = (ENV["XING_TMUX_MIN_LINES"] || MINIMUM_WINDOW_LINES).to_i
108
+ min_lines = [1, lines / min_lines].max
109
+ if calculate_layout == "tiled"
110
+ min_lines * 2
111
+ else
112
+ min_lines
113
+ end
114
+ end
115
+
116
+ def calculate_layout
117
+ min_cols = (ENV["XING_TMUX_MIN_COLS"] || MINIMUM_WINDOW_COLUMNS).to_i
118
+ cols = tput_cols
119
+ if cols > min_cols * 2
120
+ "tiled"
121
+ else
122
+ "even-vertical"
123
+ end
124
+ end
125
+
126
+ def open_new_pane(name, task)
127
+ tmux "new-window -d -n '#{name}' '#{rake_command(task)}' \\; set-window-option remain-on-exit on"
128
+ tmux "join-pane -d -s '#{name}.0' -t '#{@window_name}.bottom'"
129
+ end
130
+
131
+ def open_first_window(name, task)
132
+ if tmux('list-windows -F \'#{window_name}\'') =~ /#{name}|#{@window_name}/
133
+ puts "It looks like there are already windows open for this tmux?"
134
+ exit 2
135
+ end
136
+
137
+ if existing?
138
+ tmux "new-window -n '#@window_name' '#{rake_command(task)}' \\; set-window-option remain-on-exit on"
139
+ else
140
+ tmux "new-session -d -n '#@window_name' '#{rake_command(task)}' \\; set-window-option remain-on-exit on"
141
+ end
142
+ end
143
+
144
+ def open_additional_window(name, task)
145
+ tmux "select-layout -t '#@window_name' #{@layout}"
146
+ @window_count = @window_count + 1
147
+ @window_name = "Dev Servers #{@window_count}"
148
+ tmux "new-window -d -n '#@window_name' '#{rake_command(task)}' \\; set-window-option remain-on-exit on"
149
+ @pane_count = 0
150
+ end
151
+
152
+ def start_child(name, task)
153
+ if @first_child
154
+ open_first_window(name, task)
155
+ elsif @pane_count >= new_window_after
156
+ open_additional_window(name, task)
157
+ else
158
+ open_new_pane(name, task)
159
+ end
160
+ @pane_count = @pane_count + 1
161
+ @first_child = false
162
+ end
163
+
164
+ def wait_all
165
+ tmux "select-layout -t '#@window_name' #{@layout}"
166
+ super
167
+ end
168
+ end
169
+
170
+ class TmuxWindow < Tmux
171
+ def start_child(name, task)
172
+ if @first_child
173
+ if tmux 'list-windows -F \'#{window_name}\'' =~ /#{name}/
174
+ puts "It looks like there are already windows open for this tmux?"
175
+ exit 2
176
+ end
177
+ end
178
+
179
+ if @first_child and not existing?
180
+ tmux "new-session -d -n '#{name}' '#{rake_command(task)}' \\; set-window-option remain-on-exit on"
181
+ else
182
+ tmux "new-window -n '#{name}' '#{rake_command(task)}' \\; set-window-option remain-on-exit on"
183
+ end
184
+ @first_child = false
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,45 @@
1
+ require 'xing/edicts'
2
+ require 'xing/tasks/tasklib'
3
+
4
+ module Xing
5
+ module Tasks
6
+ class Backend < Tasklib
7
+ default_namespace :backend
8
+ setting :dir, "backend"
9
+
10
+ def define
11
+ in_namespace do
12
+ edict_task :bundle_install, Edicts::CleanRun do |bi|
13
+ bi.shell_cmd = %w{bundle check || bundle install}
14
+ end
15
+
16
+ edict_task :check_dependencies, Edicts::CleanRun do |cd|
17
+ cd.shell_cmd = %w{bundle exec rake dependencies:check}
18
+ end
19
+ task :check_dependencies => :bundle_install
20
+
21
+ desc "Migrate database up to current"
22
+ edict_task :db_migrate, Edicts::CleanRun do |dm|
23
+ dm.shell_cmd = %w{bundle exec rake db:migrate}
24
+ end
25
+ task :db_migrate => :bundle_install
26
+
27
+ task :setup => [:bundle_install, :db_migrate]
28
+
29
+ edict_task :db_seed, Edicts::CleanRun do |ds|
30
+ ds.shell_cmd = %w{bundle exec rake db:seed}
31
+ end
32
+ task :db_seed => :db_migrate
33
+
34
+ desc "Precompile rails assets"
35
+ edict_task :assets_precompile, Edicts::CleanRun do |ap|
36
+ ap.shell_cmd = %w{bundle exec rake assets:precompile}
37
+ end
38
+ task :assets_precompile => [:bundle_install, :db_migrate]
39
+
40
+ task :all => [:db_seed, :assets_precompile]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,28 @@
1
+ require 'xing/edicts'
2
+ require 'xing/tasks/tasklib'
3
+
4
+ module Xing
5
+ module Tasks
6
+ class Build < Tasklib
7
+ default_namespace :build
8
+ setting :config_dir, "../frontend"
9
+
10
+ def define
11
+ in_namespace do
12
+ task :all => ['frontend:all']
13
+
14
+ namespace :frontend do
15
+ edict_task :grunt_compile, Edicts::CleanRun do |gc|
16
+ gc.dir = "frontend"
17
+ gc.env_hash = {"CUSTOM_CONFIG_DIR" => config_dir}
18
+ gc.shell_cmd = %w{bundle exec node_modules/.bin/grunt compile}
19
+ end
20
+ task :grunt_compile => ['frontend:setup']
21
+
22
+ task :all => [:grunt_compile]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end