Initial import
This commit is contained in:
11
lib/picop.rb
Normal file
11
lib/picop.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "picop/version"
|
||||
require_relative "picop/source_file"
|
||||
require_relative "picop/scanner"
|
||||
require_relative "picop/cli"
|
||||
|
||||
module Picop
|
||||
class Error < StandardError; end
|
||||
# Your code goes here...
|
||||
end
|
||||
88
lib/picop/cli.rb
Normal file
88
lib/picop/cli.rb
Normal file
@@ -0,0 +1,88 @@
|
||||
require "optparse"
|
||||
|
||||
module Picop
|
||||
class CLI
|
||||
def self.run(argv = ARGV)
|
||||
command = argv.shift
|
||||
case command
|
||||
when 'scan'
|
||||
options = {}
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = "Usage: picop scan [options] DIRECTORY"
|
||||
opts.on('-v', '--verbose', 'Run verbosely') { |v| options[:verbose] = v }
|
||||
end.parse!(argv)
|
||||
|
||||
dir = argv.first || '.'
|
||||
Picop::Scanner.scan(dir, options)
|
||||
|
||||
when 'sign'
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = "Usage: picop sign FILE"
|
||||
end.parse!(argv)
|
||||
|
||||
file = argv.first
|
||||
Picop::SourceFile.new(file).sign
|
||||
|
||||
when 'checksum'
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = "Usage: picop checksum FILE"
|
||||
end.parse!(argv)
|
||||
file = argv.first
|
||||
puts Picop::SourceFile.new(file).checksum
|
||||
|
||||
when 'verify'
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = "Usage: picop sign FILE"
|
||||
end.parse!(argv)
|
||||
|
||||
path = argv.first
|
||||
source = SourceFile.new(path)
|
||||
|
||||
if source.metadata['content_checksum'].nil?
|
||||
puts "⚠️ No checksum found in #{path}"
|
||||
puts "Run 'picop sign #{path}' to add one"
|
||||
exit 1
|
||||
end
|
||||
|
||||
unless source.verify
|
||||
puts "❌ Checksum verification failed for #{path}"
|
||||
puts "Expected: #{source.metadata['content_checksum']}"
|
||||
puts "Got: #{source.checksum}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "✅ #{path} verified successfully"
|
||||
|
||||
when 'show'
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = "Usage: picop show FILE|DIRECTORY"
|
||||
end.parse!(argv)
|
||||
|
||||
path = argv.first
|
||||
Picop.show(path)
|
||||
|
||||
when 'update'
|
||||
options = {}
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = "Usage: picop update [options] FILE"
|
||||
opts.on('-f', '--force', 'Force update') { |f| options[:force] = f }
|
||||
end.parse!(argv)
|
||||
|
||||
file = argv.first
|
||||
Picop.update(file, options)
|
||||
|
||||
else
|
||||
puts "Unknown command: #{command}"
|
||||
puts "Available commands: scan, sign, show, update"
|
||||
exit 1
|
||||
end
|
||||
rescue OptionParser::InvalidOption => e
|
||||
puts e.message
|
||||
exit 1
|
||||
rescue => e
|
||||
puts "Error: #{e.message}"
|
||||
puts e.backtrace if ENV['DEBUG']
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
end
|
||||
11
lib/picop/scanner.rb
Normal file
11
lib/picop/scanner.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
module Picop
|
||||
module Scanner
|
||||
def self.scan(directory, pattern: "**/*")
|
||||
Dir.glob(File.join(directory, pattern)).select do |file|
|
||||
next unless File.file?(file)
|
||||
content = File.read(file)
|
||||
content.match?(SourceFile::METADATA_PATTERN)
|
||||
end.map { |file| SourceFile.new(file) }
|
||||
end
|
||||
end
|
||||
end
|
||||
65
lib/picop/source_file.rb
Normal file
65
lib/picop/source_file.rb
Normal file
@@ -0,0 +1,65 @@
|
||||
require "yaml"
|
||||
require "digest"
|
||||
|
||||
module Picop
|
||||
class SourceFile
|
||||
attr_reader :file_path, :content
|
||||
|
||||
METADATA_PATTERN = /^\s*#\s*@META_START\n(.*?)^\s*#\s*@META_END/m
|
||||
|
||||
def initialize(file_path)
|
||||
@file_path = file_path
|
||||
@content = File.read(file_path)
|
||||
end
|
||||
|
||||
def metadata
|
||||
@metadata ||= begin
|
||||
return {} unless content =~ METADATA_PATTERN
|
||||
|
||||
yaml_content = $1.lines.map do |line|
|
||||
line.sub(/^\s*#\s?/, '').rstrip
|
||||
end.join("\n")
|
||||
|
||||
YAML.safe_load(yaml_content)
|
||||
end
|
||||
end
|
||||
|
||||
def file_content = content.sub(/^\s*#\s*@META_START\n.*?^\s*#\s*@META_END\n*/m, '')
|
||||
|
||||
def checksum = "sha256:#{Digest::SHA256.hexdigest(file_content)}"
|
||||
|
||||
def show = puts(metadata.merge({checksum: checksum}))
|
||||
|
||||
def save = File.write(file_path, content)
|
||||
|
||||
def add_metadata(metadata_hash)
|
||||
yaml_content = metadata_hash.to_yaml.strip
|
||||
metadata_block = [
|
||||
"# @META_START",
|
||||
yaml_content.lines.map { |line| "# #{line}" }.join,
|
||||
"# @META_END"
|
||||
].join("\n")
|
||||
|
||||
if content =~ METADATA_PATTERN
|
||||
@content = content.sub(METADATA_PATTERN, metadata_block)
|
||||
else
|
||||
@content = [metadata_block, content].join("\n\n")
|
||||
end
|
||||
end
|
||||
|
||||
def sign
|
||||
hash = checksum
|
||||
meta = metadata || {}
|
||||
return puts "File already signed" if meta['content_checksum'] == hash
|
||||
|
||||
meta['content_checksum'] = "#{hash}"
|
||||
add_metadata(meta)
|
||||
save
|
||||
end
|
||||
|
||||
def verify
|
||||
return false unless metadata.key? 'content_checksum'
|
||||
checksum == metadata['content_checksum']
|
||||
end
|
||||
end
|
||||
end
|
||||
5
lib/picop/version.rb
Normal file
5
lib/picop/version.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Picop
|
||||
VERSION = "0.1.0"
|
||||
end
|
||||
Reference in New Issue
Block a user