Module: Vivlio::Starter::CLI::NewCommands

Extended by:
NewCommands
Included in:
NewCommands
Defined in:
lib/vivlio/starter/cli/new.rb

Constant Summary collapse

SCAFFOLD_SOURCE =

‘lib/project_scaffold/` への絶対パス(`cli/new.rb` 基準)

File.expand_path('../../../project_scaffold', __dir__).freeze
GEM_ROOT =
File.expand_path('../../../..', __dir__).freeze
VALID_NAME_PATTERN =
/\A[a-zA-Z0-9_-]+\z/
DEFAULT_ANSWERS =
{
  main_title: '新しい本',
  subtitle: '',
  author: '',
  publisher: ''
}.freeze

Instance Method Summary collapse

Instance Method Details

#execute_new(name, options = {}) ⇒ Object

旧来のオプション付き新規作成(内部・テスト用)



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/vivlio/starter/cli/new.rb', line 46

def execute_new(name, options = {})
  ENV['VERBOSE'] = '1' if options[:verbose]

  name = name&.strip
  if name.nil? || name.empty?
    Common.log_error('Error: プロジェクト名を指定してください。例: vs new mybook')
    exit(1)
  end

  dest = File.expand_path(name)
  if File.exist?(dest)
    Common.log_error("Error: '#{name}' は既に存在します。別名を指定してください。")
    exit(1)
  end

  Common.log_action("[vivlio-starter] Creating new project: #{name}")

  result = Vivlio::Starter::Scaffolder.scaffold_project(
    name: name,
    dest: dest,
    gem_root: GEM_ROOT,
    copy_styles_mode: :all,
    include_ci_workflow: true,
    include_viv_config_update: true
  )

  Common.log_success("[vivlio-starter] Done. cd #{name} で移動し、執筆を開始できます。")
  Common.log_info('例: vivliostyle preview などのコマンドを実行')

  begin
    Dir.chdir(result.dest) do
      if options[:manual_install]
        Common.log_always('doctor の自動実行をスキップします (--manual-install)')
      elsif options[:auto_install]
        Common.log_always('必要ツールの自動インストールを有効にして doctor を実行します (--auto-install)')
        opts = { fix: true }
        opts[:yes] = true unless options[:interactive]
        DoctorCommands.execute_doctor({ options: opts })
      else
        proceed = false
        if $stdin.tty?
          $stdout.print('qpdf / pdfinfo の診断を実行しますか? [y/N]: ')
          ans = $stdin.gets
          proceed = ans && ans.strip.downcase == 'y'
        end
        if proceed
          DoctorCommands.execute_doctor({})
        else
          Common.log_always('後で実行する場合: vs doctor もしくは vs doctor --fix (macOS)')
        end
      end
    end
  rescue StandardError => e
    Common.log_warn("doctor 実行フローでエラーが発生しました: #{e}")
  end
end

#run(name) ⇒ Object

Scaffolder ベースのプロジェクト作成(README 同梱・book.yml 対話更新・vivliostyle 同期)



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/vivlio/starter/cli/new.rb', line 104

def run(name)
  name = (name || '').strip
  abort 'Error: プロジェクト名を指定してください。例: vs new mybook' if name.empty?

  dest = File.expand_path(name)
  abort "Error: '#{name}' は既に存在します。別名を指定してください。" if File.exist?(dest)

  puts "[vivlio-starter] Creating new project: #{name}"

  result = Vivlio::Starter::Scaffolder.scaffold_project(
    name: name,
    dest: dest,
    gem_root: GEM_ROOT,
    include_post_replace: true,
    include_readme: true,
    readme_content: default_readme_content(name),
    include_viv_config_update: true
  ) do |event, ctx|
    update_book_config_interactively(ctx[:config_path]) if event == :after_config
  end

  run_vs_config(result.dest)
  synchronize_viv_config(result.vivliostyle_config_path, result.config_path)

  Common.log_always "[vivlio-starter] 完了しました。cd #{name} で移動し、執筆を開始できます。"
  Common.log_always '例: vivlio-starter build で執筆した書籍をPDFで作成できます。'
  0
end

#run_from_command(cmd) ⇒ Object

Samovar ‘NewCommand` から呼び出すメイン処理(終了コードを返す)



32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/vivlio/starter/cli/new.rb', line 32

def run_from_command(cmd)
  return cmd.print_usage if cmd.options[:help]

  project_name = validated_project_name!(cmd)
  check_existing_directory!(cmd, project_name)

  answers = collect_answers(cmd, project_name)
  expand_scaffold(cmd, project_name, answers)
  run_doctor(cmd, project_name)
  print_success(project_name)
  0
end

#update_book_config_interactively(config_path) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/vivlio/starter/cli/new.rb', line 133

def update_book_config_interactively(config_path)
  return unless $stdin.tty?

  cfg = YAML.load_file(config_path)
  cfg = {} unless cfg.is_a?(Hash)
  book_cfg = cfg['book'] || {}

  puts "\n[vivlio-starter] 書籍情報を入力してください(未入力は現状の値を維持)"
  current_title = book_cfg['main_title'].to_s
  current_sub   = book_cfg['subtitle'].to_s
  current_auth  = book_cfg['author'].to_s

  new_title = prompt_with_default('書籍名(main_title)', current_title)
  new_sub   = prompt_with_default('副題(subtitle)', current_sub)
  new_auth  = prompt_with_default('著者名(author)', current_auth)

  text = File.read(config_path, encoding: 'utf-8')
  lines = text.lines
  book_idx = lines.index { |l| l =~ /^\s*book:\s*$/ }

  keys = {
    'main_title' => new_title,
    'subtitle' => new_sub,
    'author' => new_auth
  }

  if book_idx
    end_idx = lines.length
    ((book_idx + 1)...lines.length).each do |i|
      if lines[i] =~ /^\S/ && lines[i] !~ /^\s{2}/
        end_idx = i
        break
      end
    end

    present = { 'main_title' => false, 'subtitle' => false, 'author' => false }

    ((book_idx + 1)...end_idx).each do |i|
      line = lines[i]
      next unless line =~ /^(\s{2})(main_title|subtitle|author):\s*([^#\n]*)(\s*#.*)?$/

      indent  = ::Regexp.last_match(1)
      key     = ::Regexp.last_match(2)
      comment = ::Regexp.last_match(4).to_s
      value   = keys[key]
      lines[i] = "#{indent}#{key}: '#{value}'#{comment}\n"
      present[key] = true
    end

    insert_pos = book_idx + 1
    %w[main_title subtitle author].each do |key|
      next if present[key]

      lines.insert(insert_pos, "  #{key}: '#{keys[key]}'\n")
      insert_pos += 1
    end

    File.write(config_path, lines.join, encoding: 'utf-8')
    Common.log_success("[vivlio-starter] book.yml を更新しました。")
  else
    File.open(config_path, 'a:utf-8') do |f|
      f.puts
      f.puts 'book:'
      f.puts "  main_title: '#{new_title}'"
      f.puts "  subtitle: '#{new_sub}'"
      f.puts "  author: '#{new_auth}'"
    end
    Common.log_success("[vivlio-starter] book.yml に book セクションを追記しました。")
  end
rescue StandardError => e
  Common.log_warn("[vivlio-starter] book.yml の対話入力に失敗しました: #{e}")
end