lhj-tools 0.1.4 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/lhj/action/sh_helper.rb +138 -0
- data/lib/lhj/command/config/info.rb +47 -0
- data/lib/lhj/command/config.rb +11 -0
- data/lib/lhj/command/file_path.rb +20 -0
- data/lib/lhj/command/head_import.rb +19 -3
- data/lib/lhj/command/http.rb +14 -0
- data/lib/lhj/command/init.rb +2 -2
- data/lib/lhj/command/local/fetch.rb +1 -1
- data/lib/lhj/command/local/filter.rb +1 -1
- data/lib/lhj/command/local/local.rb +1 -1
- data/lib/lhj/command/local/local_upload.rb +1 -1
- data/lib/lhj/command/local/micro_service.rb +1 -1
- data/lib/lhj/command/oss/del.rb +18 -5
- data/lib/lhj/command/oss/list.rb +6 -2
- data/lib/lhj/command/oss/upload.rb +9 -5
- data/lib/lhj/command/refactor_rename.rb +18 -2
- data/lib/lhj/command/rename_image.rb +1 -1
- data/lib/lhj/command/sync_pod_repo.rb +151 -0
- data/lib/lhj/command/trans.rb +1 -1
- data/lib/lhj/command/yapi.rb +15 -14
- data/lib/lhj/command.rb +33 -0
- data/lib/lhj/tools/version.rb +1 -1
- data/lib/lhj/tools.rb +1 -0
- data/lib/lhj/tree/directory_renderer.rb +45 -0
- data/lib/lhj/tree/hash_walker.rb +70 -0
- data/lib/lhj/tree/node.rb +90 -0
- data/lib/lhj/tree/number_renderer.rb +30 -0
- data/lib/lhj/tree/path_walker.rb +107 -0
- data/lib/lhj/tree/tree.rb +112 -0
- data/lib/lhj/ui/errors/lhj_common_error.rb +19 -0
- data/lib/lhj/ui/errors/lhj_crash.rb +11 -0
- data/lib/lhj/ui/errors/lhj_error.rb +25 -0
- data/lib/lhj/ui/errors/lhj_exception.rb +19 -0
- data/lib/lhj/ui/errors/lhj_shell_error.rb +11 -0
- data/lib/lhj/ui/errors.rb +1 -0
- data/lib/lhj/ui/implementations/shell.rb +148 -0
- data/lib/lhj/ui/interface.rb +205 -0
- data/lib/lhj/ui/ui.rb +26 -0
- metadata +77 -2
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'lhj/config'
|
2
|
+
require 'highline'
|
3
|
+
|
4
|
+
module Lhj
|
5
|
+
class Command
|
6
|
+
# sync code to pod
|
7
|
+
class SyncPod < Command
|
8
|
+
self.summary = '同步代码到私用仓库'
|
9
|
+
|
10
|
+
def initialize(argv)
|
11
|
+
@cli = HighLine.new
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def handle
|
16
|
+
sync
|
17
|
+
end
|
18
|
+
|
19
|
+
def sync
|
20
|
+
config_file = File.join(Lhj::Config.instance.home_dir, 'pod_config.yml')
|
21
|
+
arr = YAML.load_file(config_file)
|
22
|
+
arr.each_index { |i| puts "#{i}.#{arr[i]['pod']}".yellow }
|
23
|
+
idx = @cli.ask('请选择哪一个库同步: '.green).strip.to_i
|
24
|
+
pod_name = arr[idx]['pod']
|
25
|
+
src = arr[idx]['main_path']
|
26
|
+
dest = arr[idx]['pod_path']
|
27
|
+
FileUtils.cp_r(src, dest, remove_destination: true)
|
28
|
+
puts '1.从主工程复制代码到pod库成功'.green
|
29
|
+
|
30
|
+
ma = nil
|
31
|
+
Dir.chdir(dest) do
|
32
|
+
Dir.glob('*.podspec').each do |p|
|
33
|
+
update_podspec_version(p)
|
34
|
+
version_line = IO.readlines(p).find{ |line| (/\.version/ =~ line) && (version_regex =~ line) }
|
35
|
+
ma = version_line.match(version_regex)
|
36
|
+
end
|
37
|
+
puts '2.更新版本号成功'.green
|
38
|
+
|
39
|
+
Actions.sh('git add .')
|
40
|
+
puts '3.git add成功'.green
|
41
|
+
|
42
|
+
Actions.sh("git commit -m '同步主工程代码by fastlane'")
|
43
|
+
puts '4.git 提交成功'.green
|
44
|
+
|
45
|
+
Actions.sh('git push')
|
46
|
+
puts '5.git 推送成功'.green
|
47
|
+
|
48
|
+
add_tag(ma[0]) if ma
|
49
|
+
puts "6.设置tag成功! tag号:#{ma[0]}".green
|
50
|
+
|
51
|
+
push_tag
|
52
|
+
puts '7.推送tag成功'.green
|
53
|
+
|
54
|
+
update_pod_repo
|
55
|
+
puts "8.#{pod_name}推送pod repo成功".green
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
# find src root dir
|
60
|
+
src_root_dir = find_src_root_dir(src)
|
61
|
+
Dir.chdir(src_root_dir) do
|
62
|
+
if ma
|
63
|
+
pod_version = ma[0]
|
64
|
+
update_all_pod_dependency(pod_name, pod_version)
|
65
|
+
puts '9.更新主工程引用pod版本号成功'.green
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
puts '10.手动执行`pod update --verbose --no-repo-update`更新pod'.green
|
70
|
+
end
|
71
|
+
|
72
|
+
def find_src_root_dir(src)
|
73
|
+
ps = []
|
74
|
+
src.scan(%r{(/[^/]*)}).flatten.each do |path|
|
75
|
+
path =~ /Code/ ? break : ps << path
|
76
|
+
end
|
77
|
+
File.join(ps)
|
78
|
+
end
|
79
|
+
|
80
|
+
def version_regex
|
81
|
+
/\d+\.\d+\.(\d+)/
|
82
|
+
end
|
83
|
+
|
84
|
+
def update_podspec_version(path)
|
85
|
+
str = ''
|
86
|
+
File.readlines(path).each do |l|
|
87
|
+
if (/\.version/ =~ l) && (version_regex =~ l)
|
88
|
+
last_version = l.scan(version_regex).flatten.first
|
89
|
+
next_version = last_version.to_i + 1
|
90
|
+
next_version_str = next_version.to_s
|
91
|
+
str += l.gsub(/(\d+\.\d+\.)(\d+)/, '\1' + next_version_str)
|
92
|
+
else
|
93
|
+
str += l.dup
|
94
|
+
end
|
95
|
+
end
|
96
|
+
File.write(path, str)
|
97
|
+
end
|
98
|
+
|
99
|
+
def add_tag(tag)
|
100
|
+
cmd = ['git tag']
|
101
|
+
cmd << '--force'
|
102
|
+
cmd << tag
|
103
|
+
UI.message("Adding git tag '#{tag}' 🎯.")
|
104
|
+
Actions.sh(cmd.join(' '))
|
105
|
+
end
|
106
|
+
|
107
|
+
def push_tag
|
108
|
+
cmd = ['git push --tags']
|
109
|
+
UI.message('git push --tags 🎯.')
|
110
|
+
Actions.sh(cmd.join(' '))
|
111
|
+
end
|
112
|
+
|
113
|
+
def update_pod_repo
|
114
|
+
# pod repo push miguatech-aomi_ios-mlspecs *.podspec --sources=miguatech-aomi_ios-mlspecs,aliyun,trunk --allow-warnings --use-libraries --verbose
|
115
|
+
cmd = ['pod repo push']
|
116
|
+
pod_repo_name = 'miguatech-aomi_ios-mlspecs'
|
117
|
+
cmd << pod_repo_name
|
118
|
+
cmd << '*.podspec'
|
119
|
+
cmd << "--sources=#{pod_repo_name},aliyun,trunk"
|
120
|
+
cmd << '--allow-warnings'
|
121
|
+
cmd << '--use-libraries'
|
122
|
+
cmd << '--verbose'
|
123
|
+
Actions.sh(cmd.join(' '), log: true, error_callback: nil)
|
124
|
+
end
|
125
|
+
|
126
|
+
def update_all_pod_dependency(pod_name, pod_version)
|
127
|
+
Dir.glob(%w[./**/*.podspec ./**/Podfile]).each do |p|
|
128
|
+
next if /Example/ =~ p || /temp/ =~ p
|
129
|
+
|
130
|
+
update_main_version(p, pod_name, pod_version)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def update_main_version(file, pod_name, pod_version)
|
135
|
+
cont = ''
|
136
|
+
File.readlines(file).each do |l|
|
137
|
+
if (/#{pod_name}/ =~ l) && (/\d+\.\d+\.\d+/ =~ l)
|
138
|
+
path = File.absolute_path(file)
|
139
|
+
puts "更新文件:#{path}".magenta
|
140
|
+
l.scan(/\d+\.\d+\.\d+/).each do |key|
|
141
|
+
cont += l.gsub(key, pod_version)
|
142
|
+
end
|
143
|
+
else
|
144
|
+
cont += l.dup
|
145
|
+
end
|
146
|
+
end
|
147
|
+
File.write(file, cont)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/lib/lhj/command/trans.rb
CHANGED
data/lib/lhj/command/yapi.rb
CHANGED
@@ -5,6 +5,7 @@ require 'yaml'
|
|
5
5
|
|
6
6
|
module Lhj
|
7
7
|
class Command
|
8
|
+
# generate model from yapi
|
8
9
|
class Yapi < Command
|
9
10
|
self.summary = '通过yapi接口生成请求'
|
10
11
|
self.description = '更新 ~/.lhj/yapi.yml 文件配置后执行`lhj api`生成接口模型'
|
@@ -34,7 +35,7 @@ module Lhj
|
|
34
35
|
super
|
35
36
|
end
|
36
37
|
|
37
|
-
def
|
38
|
+
def handle
|
38
39
|
load_config
|
39
40
|
fetch_model
|
40
41
|
print_methods
|
@@ -49,13 +50,13 @@ module Lhj
|
|
49
50
|
end
|
50
51
|
|
51
52
|
def puts_h(str)
|
52
|
-
puts str
|
53
|
+
puts str.magenta
|
53
54
|
@h_file_array ||= []
|
54
55
|
@h_file_array << str
|
55
56
|
end
|
56
57
|
|
57
58
|
def puts_m(str)
|
58
|
-
puts str
|
59
|
+
puts str.blue
|
59
60
|
@m_file_array ||= []
|
60
61
|
@m_file_array << str
|
61
62
|
end
|
@@ -65,9 +66,9 @@ module Lhj
|
|
65
66
|
file_name = gen_model_name('')
|
66
67
|
h_file = File.join('.', "#{file_name}.h")
|
67
68
|
m_file = File.join('.', "#{file_name}.m")
|
68
|
-
File.write(h_file, @h_file_array.join("\n")) if @h_file_array.count
|
69
|
-
File.write(m_file, @m_file_array.join("\n")) if @m_file_array.count
|
70
|
-
puts "\n\n生成文件成功!所在路径:\n#{File.expand_path(h_file)} \n#{File.expand_path(m_file)}"
|
69
|
+
File.write(h_file, @h_file_array.join("\n")) if @h_file_array.count.positive?
|
70
|
+
File.write(m_file, @m_file_array.join("\n")) if @m_file_array.count.positive?
|
71
|
+
puts "\n\n生成文件成功!所在路径:\n#{File.expand_path(h_file)} \n#{File.expand_path(m_file)}".green
|
71
72
|
end
|
72
73
|
|
73
74
|
def url_str
|
@@ -78,7 +79,7 @@ module Lhj
|
|
78
79
|
yml = File.join(Lhj::Config.instance.home_dir, 'yapi.yml')
|
79
80
|
config = YAML.load_file(yml)
|
80
81
|
config.each do |k, v|
|
81
|
-
@http_headers << "#{k}=#{v}" if
|
82
|
+
@http_headers << "#{k}=#{v}" if k.eql?('__wpkreporterwid_') || k.eql?('_yapi_token') || k.eql?('_yapi_uid')
|
82
83
|
end
|
83
84
|
@http_url = config['url']
|
84
85
|
@config_id = config['id']
|
@@ -107,27 +108,27 @@ module Lhj
|
|
107
108
|
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
|
108
109
|
http.request(req)
|
109
110
|
end
|
110
|
-
puts res.body
|
111
|
+
puts res.body unless res.body['errcode'].to_i.zero?
|
111
112
|
JSON.parse(res.body)
|
112
113
|
end
|
113
114
|
|
114
115
|
def fetch_model
|
115
116
|
res_json = req_model
|
116
117
|
begin
|
117
|
-
puts "\n<===============打印返回数据模型-Begin=====================>\n"
|
118
|
+
puts "\n<===============打印返回数据模型-Begin=====================>\n".green
|
118
119
|
fetch_res_boy(res_json)
|
119
120
|
print_models
|
120
121
|
print_models_implementation
|
121
|
-
puts "\n<===============打印返回数据模型-End=====================>\n"
|
122
|
+
puts "\n<===============打印返回数据模型-End=====================>\n".green
|
122
123
|
end
|
123
124
|
begin
|
124
|
-
puts "\n<===============打印请求模型-Begin=====================>\n"
|
125
|
+
puts "\n<===============打印请求模型-Begin=====================>\n".green
|
125
126
|
@models = []
|
126
127
|
@model_names = []
|
127
128
|
fetch_req_body(res_json)
|
128
129
|
print_models
|
129
130
|
print_models_implementation
|
130
|
-
puts "\n<===============打印请求模型-End=====================>\n"
|
131
|
+
puts "\n<===============打印请求模型-End=====================>\n".green
|
131
132
|
end
|
132
133
|
end
|
133
134
|
|
@@ -221,7 +222,7 @@ module Lhj
|
|
221
222
|
@models.each do |model|
|
222
223
|
puts_m "@implementation #{model[:name]}"
|
223
224
|
str = model[:properties].filter { |p| p[:type].eql?('array') && !p[:type_name].eql?('NSString') }.map { |p| "@\"#{p[:key]}\": #{p[:type_name]}.class" }.join(', ')
|
224
|
-
if str && str.length
|
225
|
+
if str && str.length.positive?
|
225
226
|
puts_m '+(NSDictionary *)modelContainerPropertyGenericClass {'
|
226
227
|
puts_m " return @{#{str}};"
|
227
228
|
puts_m '}'
|
@@ -268,7 +269,7 @@ module Lhj
|
|
268
269
|
end
|
269
270
|
|
270
271
|
def print_methods
|
271
|
-
puts "\n<===============方法调用=====================>\n"
|
272
|
+
puts "\n<===============方法调用=====================>\n".green
|
272
273
|
puts_m '/**'
|
273
274
|
puts_m " * #{@data_json['title']} -- #{@data_json['username']}"
|
274
275
|
puts_m ' */'
|
data/lib/lhj/command.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
require 'claide'
|
2
|
+
require "tty-spinner"
|
3
|
+
require 'lhj/action/sh_helper'
|
4
|
+
require 'lhj/ui/ui'
|
2
5
|
|
3
6
|
module Lhj
|
4
7
|
# command plugin
|
5
8
|
class Command < CLAide::Command
|
6
9
|
require 'lhj/command/init'
|
10
|
+
require 'lhj/command/config'
|
7
11
|
require 'lhj/command/head_import'
|
8
12
|
require 'lhj/command/refactor_rename'
|
9
13
|
require 'lhj/command/local/fetch'
|
@@ -19,8 +23,37 @@ module Lhj
|
|
19
23
|
require 'lhj/command/rename_image'
|
20
24
|
require 'lhj/command/trans'
|
21
25
|
require 'lhj/command/yapi'
|
26
|
+
require 'lhj/command/file_path'
|
27
|
+
require 'lhj/command/http'
|
28
|
+
require 'lhj/command/sync_pod_repo'
|
22
29
|
|
23
30
|
self.abstract_command = true
|
24
31
|
self.command = 'lhj'
|
32
|
+
|
33
|
+
def auto_spin
|
34
|
+
print "正在处理...\n".green
|
35
|
+
# @spinner.auto_spin
|
36
|
+
end
|
37
|
+
|
38
|
+
def stop
|
39
|
+
print '处理完成'.green
|
40
|
+
# @spinner.success('Done!')
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(argv)
|
44
|
+
super(argv)
|
45
|
+
@spinner = TTY::Spinner.new('...', output: $stdout, format: :dots, clear: true)
|
46
|
+
end
|
47
|
+
|
48
|
+
def run
|
49
|
+
auto_spin
|
50
|
+
handle
|
51
|
+
stop
|
52
|
+
end
|
53
|
+
|
54
|
+
def handle
|
55
|
+
raise 'A subclass should override the `Lhj::Command#run` method'
|
56
|
+
end
|
57
|
+
|
25
58
|
end
|
26
59
|
end
|
data/lib/lhj/tools/version.rb
CHANGED
data/lib/lhj/tools.rb
CHANGED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lhj
|
4
|
+
class Tree
|
5
|
+
# Render nodes as files paths explorer
|
6
|
+
class DirectoryRenderer
|
7
|
+
MARKERS = {
|
8
|
+
unicode: {
|
9
|
+
branch: '├──',
|
10
|
+
leaf: '└──',
|
11
|
+
pipe: '│'
|
12
|
+
},
|
13
|
+
ansi: {
|
14
|
+
branch: '|--',
|
15
|
+
leaf: '`--',
|
16
|
+
pipe: '|'
|
17
|
+
}
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
def initialize(nodes, options = {})
|
21
|
+
@nodes = nodes
|
22
|
+
@indent = options.fetch(:indent, 4)
|
23
|
+
@pipe_mark = MARKERS[:unicode][:pipe] + ' ' * [@indent - 1, 0].max
|
24
|
+
@space_mark = ' ' * @indent
|
25
|
+
end
|
26
|
+
|
27
|
+
def render
|
28
|
+
@nodes.reduce([]) do |acc, node|
|
29
|
+
render_node(acc, node, @pipe_mark, @space_mark)
|
30
|
+
end.join('')
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def render_node(acc, node, pipe_mark, space_mark)
|
36
|
+
acc << node.prefix.gsub(/:pipe/, pipe_mark).gsub(/:space/, space_mark)
|
37
|
+
unless node.root?
|
38
|
+
acc << MARKERS[:unicode][node.leaf? ? :leaf : :branch]
|
39
|
+
acc << ' '
|
40
|
+
end
|
41
|
+
acc << node.name.to_s + "\n"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
require_relative 'node'
|
6
|
+
|
7
|
+
module Lhj
|
8
|
+
class Tree
|
9
|
+
# Walk and collect nodes from hash data strcture.
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
class HashWalker
|
13
|
+
attr_reader :nodes
|
14
|
+
|
15
|
+
attr_reader :files_count
|
16
|
+
|
17
|
+
attr_reader :dirs_count
|
18
|
+
|
19
|
+
def initialize(options = {})
|
20
|
+
@nodes = []
|
21
|
+
@files_count = 0
|
22
|
+
@dirs_count = 0
|
23
|
+
@level = options.fetch(:level) { -1 }
|
24
|
+
@file_limit = options.fetch(:file_limit) { - 1 }
|
25
|
+
end
|
26
|
+
|
27
|
+
def traverse(data)
|
28
|
+
walk(data, Pathname.new(''), '', 0, false)
|
29
|
+
end
|
30
|
+
|
31
|
+
def walk(data, parent_path, prefix, level, is_last)
|
32
|
+
node = is_last ? LeafNode : Node
|
33
|
+
|
34
|
+
case data
|
35
|
+
when Hash
|
36
|
+
return if @level != -1 && level + 1 > @level
|
37
|
+
|
38
|
+
data.each do |dir, item|
|
39
|
+
dir_node = node.new(dir.to_s, parent_path, prefix, level)
|
40
|
+
@nodes << dir_node
|
41
|
+
@dirs_count += 1 unless dir_node.root?
|
42
|
+
|
43
|
+
if level > 0
|
44
|
+
postfix = ':pipe'
|
45
|
+
postfix = ':space' if is_last
|
46
|
+
else
|
47
|
+
postfix = ''
|
48
|
+
end
|
49
|
+
|
50
|
+
walk(item, parent_path + dir.to_s,
|
51
|
+
prefix + postfix, level + 1, false)
|
52
|
+
end
|
53
|
+
when Array
|
54
|
+
return if @file_limit != -1 && data.size > @file_limit
|
55
|
+
|
56
|
+
last_data_index = data.size - 1
|
57
|
+
|
58
|
+
data.each_with_index do |item, i|
|
59
|
+
last = (last_data_index == i)
|
60
|
+
|
61
|
+
walk(item, parent_path, prefix, level, last)
|
62
|
+
end
|
63
|
+
else
|
64
|
+
@nodes << node.new(data.to_s, parent_path, prefix, level)
|
65
|
+
@files_count += 1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
module Lhj
|
7
|
+
class Tree
|
8
|
+
# A representation of tree node
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Node
|
12
|
+
extend Forwardable
|
13
|
+
|
14
|
+
# The base name for the directory or file
|
15
|
+
attr_reader :name
|
16
|
+
|
17
|
+
# The parent directory path
|
18
|
+
attr_reader :parent
|
19
|
+
|
20
|
+
# The require path prefix
|
21
|
+
attr_reader :prefix
|
22
|
+
|
23
|
+
# The directory depth
|
24
|
+
attr_reader :level
|
25
|
+
|
26
|
+
# The file stat
|
27
|
+
attr_reader :stat
|
28
|
+
|
29
|
+
# The current path
|
30
|
+
attr_reader :path
|
31
|
+
|
32
|
+
def_delegators :@path, :directory?, :executable?, :file?,
|
33
|
+
:symlink?, :socket?, :pipe?
|
34
|
+
|
35
|
+
def initialize(path, parent, prefix, level)
|
36
|
+
if path.is_a? String
|
37
|
+
# strip null bytes from the string to avoid throwing errors
|
38
|
+
path = path.delete("\0")
|
39
|
+
end
|
40
|
+
|
41
|
+
@path = Pathname.new(path)
|
42
|
+
@name = @path.basename
|
43
|
+
@parent = Pathname.new(parent)
|
44
|
+
@prefix = prefix
|
45
|
+
@level = level
|
46
|
+
end
|
47
|
+
|
48
|
+
def full_path
|
49
|
+
return parent if name.to_s.empty?
|
50
|
+
|
51
|
+
parent.join(name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def root?
|
55
|
+
parent.to_s.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
def hidden?
|
59
|
+
name.to_s.start_with?('.')
|
60
|
+
end
|
61
|
+
|
62
|
+
def leaf?
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
@name
|
68
|
+
end
|
69
|
+
|
70
|
+
def ==(other)
|
71
|
+
other.is_a?(self.class) && other.state_attrs == state_attrs
|
72
|
+
end
|
73
|
+
alias eql? ==
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
def state_attrs
|
78
|
+
[@name, @path, @parent, @prefix, @level]
|
79
|
+
end
|
80
|
+
|
81
|
+
ROOT = Node.new('', Pathname.new(''), '', 0).freeze
|
82
|
+
end # Node
|
83
|
+
|
84
|
+
class LeafNode < Node
|
85
|
+
def leaf?
|
86
|
+
true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lhj
|
4
|
+
class Tree
|
5
|
+
# Render nodes as numbered list
|
6
|
+
class NumberRenderer
|
7
|
+
def initialize(nodes, options = {})
|
8
|
+
@indent = options.fetch(:indent, 4)
|
9
|
+
@nodes = nodes
|
10
|
+
@mark = ' ' * @indent
|
11
|
+
end
|
12
|
+
|
13
|
+
def render
|
14
|
+
@nodes.each_with_index.reduce([]) do |acc, (node, i)|
|
15
|
+
render_node(acc, node, i, @mark)
|
16
|
+
end.join
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def render_node(acc, node, i, mark)
|
22
|
+
acc << node.prefix.gsub(/:pipe|:space/, mark)
|
23
|
+
unless node.root?
|
24
|
+
acc << "#{node.level}.#{i} "
|
25
|
+
end
|
26
|
+
acc << node.name.to_s + "\n"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require_relative 'node'
|
5
|
+
|
6
|
+
module Lhj
|
7
|
+
class Tree
|
8
|
+
# Walk and collect nodes from directory.
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
class PathWalker
|
12
|
+
attr_reader :nodes
|
13
|
+
|
14
|
+
attr_reader :files_count
|
15
|
+
|
16
|
+
attr_reader :dirs_count
|
17
|
+
|
18
|
+
# Create a PathWalker
|
19
|
+
#
|
20
|
+
# @api public
|
21
|
+
def initialize(options = {})
|
22
|
+
@files_count = 0
|
23
|
+
@dirs_count = 0
|
24
|
+
@nodes = []
|
25
|
+
@filters = []
|
26
|
+
@level = options.fetch(:level) { -1 }
|
27
|
+
@file_limit = options.fetch(:file_limit) { - 1 }
|
28
|
+
|
29
|
+
unless options[:show_hidden]
|
30
|
+
add_filter(-> (p) { !p.basename.to_s.start_with?('.') })
|
31
|
+
end
|
32
|
+
|
33
|
+
if options[:only_dirs]
|
34
|
+
add_filter(-> (p) { p.directory? })
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_filter(filter)
|
39
|
+
@filters << filter
|
40
|
+
end
|
41
|
+
|
42
|
+
# Traverse given path recursively
|
43
|
+
#
|
44
|
+
# @param [String] path
|
45
|
+
# the path to traverse
|
46
|
+
#
|
47
|
+
# @api public
|
48
|
+
def traverse(path)
|
49
|
+
root_path = Pathname.new(path)
|
50
|
+
empty_path = Pathname.new('')
|
51
|
+
|
52
|
+
unless root_path.directory?
|
53
|
+
raise ArgumentError, "#{root_path} is not a directory path"
|
54
|
+
end
|
55
|
+
|
56
|
+
@nodes << Node.new(root_path, empty_path, '', 0)
|
57
|
+
|
58
|
+
walk(root_path, root_path.children, '', 1)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# Filter entries
|
64
|
+
#
|
65
|
+
# @api private
|
66
|
+
def filter_entries(entries, filters)
|
67
|
+
return entries if filters.nil? || filters.empty?
|
68
|
+
filter = filters[0]
|
69
|
+
filter_entries(entries.select(&filter), filters[1..-1])
|
70
|
+
end
|
71
|
+
|
72
|
+
# Walk paths recursively
|
73
|
+
#
|
74
|
+
# @api private
|
75
|
+
def walk(parent_path, entries, prefix, level)
|
76
|
+
if entries.empty? || (@level != -1 && @level < level)
|
77
|
+
return
|
78
|
+
else
|
79
|
+
return if @file_limit != -1 && entries.size > @file_limit
|
80
|
+
processed_paths = filter_entries(entries, @filters).sort
|
81
|
+
last_path_index = processed_paths.size - 1
|
82
|
+
|
83
|
+
processed_paths.each_with_index do |path, i|
|
84
|
+
sub_path = path.relative_path_from(parent_path)
|
85
|
+
|
86
|
+
node = last_path_index == i ? LeafNode : Node
|
87
|
+
|
88
|
+
if path.directory?
|
89
|
+
next if @level != -1 && level + 1 > @level
|
90
|
+
|
91
|
+
@nodes << node.new(sub_path, parent_path, prefix, level)
|
92
|
+
@dirs_count += 1
|
93
|
+
|
94
|
+
postfix = ':pipe'
|
95
|
+
postfix = ':space' if i == last_path_index
|
96
|
+
|
97
|
+
walk(path, path.children, prefix + postfix, level + 1)
|
98
|
+
elsif path.file?
|
99
|
+
@nodes << node.new(path, parent_path, prefix, level)
|
100
|
+
@files_count += 1
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|