xing-root 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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