lhj-tools 0.1.3 → 0.1.7
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 +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 +48 -8
- data/lib/lhj/command/sync_pod_repo.rb +138 -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,138 @@
|
|
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
|
+
if ma
|
60
|
+
pod_version = ma[0]
|
61
|
+
update_all_pod_dependency(pod_name, pod_version)
|
62
|
+
puts '9.更新主工程引用pod版本号成功'.green
|
63
|
+
end
|
64
|
+
|
65
|
+
puts '10.手动执行`pod update --verbose --no-repo-update`更新pod'.green
|
66
|
+
end
|
67
|
+
|
68
|
+
def version_regex
|
69
|
+
/\d+\.\d+\.(\d+)/
|
70
|
+
end
|
71
|
+
|
72
|
+
def update_podspec_version(path)
|
73
|
+
str = ''
|
74
|
+
File.readlines(path).each do |l|
|
75
|
+
if (/\.version/ =~ l) && (version_regex =~ l)
|
76
|
+
last_version = l.scan(version_regex).flatten.first
|
77
|
+
next_version = last_version.to_i + 1
|
78
|
+
next_version_str = next_version.to_s
|
79
|
+
str += l.gsub(/(\d+\.\d+\.)(\d+)/, '\1' + next_version_str)
|
80
|
+
else
|
81
|
+
str += l.dup
|
82
|
+
end
|
83
|
+
end
|
84
|
+
File.write(path, str)
|
85
|
+
end
|
86
|
+
|
87
|
+
def add_tag(tag)
|
88
|
+
cmd = ['git tag']
|
89
|
+
cmd << '--force'
|
90
|
+
cmd << tag
|
91
|
+
UI.message("Adding git tag '#{tag}' 🎯.")
|
92
|
+
Actions.sh(cmd.join(' '))
|
93
|
+
end
|
94
|
+
|
95
|
+
def push_tag
|
96
|
+
cmd = ['git push --tags']
|
97
|
+
UI.message("git push --tags 🎯.")
|
98
|
+
Actions.sh(cmd.join(' '))
|
99
|
+
end
|
100
|
+
|
101
|
+
def update_pod_repo
|
102
|
+
# pod repo push miguatech-aomi_ios-mlspecs *.podspec --sources=miguatech-aomi_ios-mlspecs,aliyun,trunk --allow-warnings --use-libraries --verbose
|
103
|
+
cmd = ['pod repo push']
|
104
|
+
pod_repo_name = 'miguatech-aomi_ios-mlspecs'
|
105
|
+
cmd << pod_repo_name
|
106
|
+
cmd << "*.podspec"
|
107
|
+
cmd << "--sources=#{pod_repo_name},aliyun,trunk"
|
108
|
+
cmd << "--allow-warnings"
|
109
|
+
cmd << "--use-libraries"
|
110
|
+
cmd << "--verbose"
|
111
|
+
Actions.sh(cmd.join(' '), log: true, error_callback: nil)
|
112
|
+
end
|
113
|
+
|
114
|
+
def update_all_pod_dependency(pod_name, pod_version)
|
115
|
+
Dir.glob(["./**/*.podspec", "./**/Podfile"]).each do |p|
|
116
|
+
unless /Example/ =~ p
|
117
|
+
puts "更新文件:#{p}".magenta
|
118
|
+
update_main_proj_podspec_version(p, pod_name, pod_version)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def update_main_proj_podspec_version(path, pod_name, pod_version)
|
124
|
+
cont = ''
|
125
|
+
File.readlines(path).each do |l|
|
126
|
+
if (/#{pod_name}/ =~ l) && (/\d+\.\d+\.\d+/ =~ l)
|
127
|
+
l.scan(/\d+\.\d+\.\d+/).each do |key|
|
128
|
+
cont += l.gsub(key, pod_version)
|
129
|
+
end
|
130
|
+
else
|
131
|
+
cont += l.dup
|
132
|
+
end
|
133
|
+
end
|
134
|
+
File.write(path, cont)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
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
|