3 Commits

11 changed files with 112 additions and 66 deletions

View File

@@ -1,5 +1,9 @@
## [Unreleased]
## [0.2.0] - 2025-01-21
- Rename to from Picop to Picopackage
## [0.1.0] - 2025-01-19
- Initial release

View File

@@ -1,7 +1,7 @@
PATH
remote: .
specs:
picop (0.1.0)
picopackage (0.2.0)
digest
open-uri (~> 0.5)
yaml (~> 0.4)
@@ -70,7 +70,7 @@ PLATFORMS
DEPENDENCIES
debug
minitest (~> 5.16)
picop!
picopackage!
rake (~> 13.0)
rubocop (~> 1.21)

View File

@@ -1,8 +1,8 @@
# Picop
# Picopackage
TODO: Delete this and the text below, and describe your gem
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/picop`. To experiment with that code, run `bin/console` for an interactive prompt.
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/picopackge`. To experiment with that code, run `bin/console` for an interactive prompt.
## Installation

16
lib/picopackage.rb Normal file
View File

@@ -0,0 +1,16 @@
# frozen_string_literal: true
require_relative "picopackage/version"
require_relative "picopackage/http_fetcher"
require_relative "picopackage/provider"
require_relative "picopackage/source_file"
require_relative "picopackage/scanner"
require_relative "picopackage/fetch"
require_relative "picopackage/cli"
module Picopackage
class Error < StandardError; end
class FileTooLargeError < StandardError; end
class FetchError < StandardError; end
class LocalModificationError < StandardError; end
end

View File

@@ -15,13 +15,13 @@ module Picopackage
dir = argv.first || '.'
Picopackage::Scanner.scan(dir).each {|f| puts f.file_path }
when 'sign'
when 'digest'
OptionParser.new do |opts|
opts.banner = "Usage: ppkg sign FILE"
opts.banner = "Usage: ppkg digest FILE"
end.parse!(argv)
file = argv.first
Picopackage::SourceFile.from_file(file).sign
Picopackage::SourceFile.from_file(file).digest!
when 'checksum'
OptionParser.new do |opts|
@@ -77,14 +77,30 @@ module Picopackage
end
begin
source_file = Fetch.fetch(url, path, force: options[:force])
Fetch.fetch(url, path, force: options[:force])
rescue LocalModificationError => e
puts "Error: #{e.message}"
rescue => e
puts "Error: #{e.message}"
exit 1
end
when 'update'
options = { force: false }
OptionParser.new do |opts|
opts.banner = "Usage: ppkg update [options] FILE"
opts.on('-f', '--force', 'Force update') { |f| options[:force] = f }
end.parse!(argv)
file = argv.first
source_file = SourceFile.from_file(file)
begin
Fetch.fetch(source_file.url, path, force: options[:force])
rescue LocalModificationError => e
puts "Error: #{e.message}"
rescue => e
puts "Error: #{e.message}"
exit 1
# Optionally retry with force
# source_file = Fetch.fetch(url, destination, force: true)
end
else

View File

@@ -4,33 +4,41 @@ require 'tempfile'
require 'json'
require 'debug'
module Picop
module Picopackage
class Fetch
def self.fetch(url, destination, force: false)
raise ArgumentError, "Destination directory does not exist: #{destination}" unless Dir.exist?(destination)
debugger
provider = Provider.for(url)
file_path = File.join(destination, provider.source_file.filename)
debugger
source_file = provider.source_file
file_path = File.join(destination, source_file.filename)
if File.exist?(file_path) && force
provider.source_file.save(destination)
source_file.save(destination)
elsif File.exist?(file_path)
local_source_file = SourceFile.from_file(file_path)
status = Status.compare(local_source_file, provider.source_file)
status = Status.compare(local_source_file, source_file)
if force
provider.source_file.save(destination)
source_file.save(destination)
elsif status.modified?
raise LocalModificationError, "#{status.message}. Use -f or --force to overwrite local version"
elsif status.outdated?
puts "Updated from #{local_source_file.version} to #{provider.source_file.version}"
provider.source_file.save(destination)
puts "Updated from #{local_source_file.version} to #{source_file.version}"
source_file.save(destination)
elsif status.up_to_date?
puts status.message
end
else
provider.source_file.save(destination)
source_file.save(destination)
if source_file.imported?
source_file.digest!
puts "Picopackage created for #{source_file.filename}"
else
puts "Picopackage downloaded to #{file_path}"
end
end
provider.source_file
end
@@ -82,15 +90,15 @@ debugger
def message
case state
when :up_to_date
"File is up to date"
"Picopackage is up to date"
when :outdated
if modified?
"Local file (v#{local_version}) has modifications but remote version (v#{remote_version}) is available"
"Local Picopackage (v#{local_version}) has modifications but remote version (v#{remote_version}) is available"
else
"Local file (v#{local_version}) is outdated. Remote version: v#{remote_version}"
"Local Picopackage (v#{local_version}) is outdated. Remote version: v#{remote_version}"
end
when :modified
"Local file has been modified from original version (v#{local_version})"
"Local Picopackage has been modified from original version (v#{local_version})"
end
end
end

View File

@@ -1,4 +1,4 @@
module Picop
module Picopackage
class Provider
def self.for(url)
PROVIDERS.each do |provider|
@@ -28,7 +28,6 @@ module Picop
class DefaultProvider
MAX_SIZE = 1024 * 1024
TIMEOUT = 10
attr_reader :url, :source_file
def self.handles_url?(url) = :maybe
@@ -44,6 +43,8 @@ module Picop
def body = @body ||= fetch
def json_body = @json_body ||= JSON.parse(body)
def fetch
begin
Net::HTTP.start(@uri.host, @uri.port, use_ssl: @uri.scheme == 'https', read_timeout: TIMEOUT, open_timeout: TIMEOUT) do |http|
@@ -71,7 +72,9 @@ module Picop
end
def content
# Implement in subclass - this come from the `body`. Spliting content into code and metadata is the job of the SourceFile class
# Implement in subclass - this come from the `body`.
# Spliting content into code and metadata is the job of the SourceFile class
raise NotImplementedError
end
@@ -81,7 +84,9 @@ module Picop
end
def source_file
@source_file ||= SourceFile.from_content(content)
@source_file ||= SourceFile.from_content(
content, metadata: {'filename' => filename, 'url' => url, 'version' => '0.0.1'}
)
end
end
@@ -93,15 +98,9 @@ module Picop
"https://api.github.com/gists/#{gist_id}"
end
def content
data = JSON.parse(body)
file = data["files"].values.first["content"]
end
def content = json_body["files"].values.first["content"]
def filename
data = JSON.parse(body)
data["files"].values.first["filename"]
end
def filename = json_body["files"].values.first["filename"]
end
class OpenGistProvider < DefaultProvider
@@ -109,19 +108,11 @@ module Picop
:maybe
end
def transform_url(url)
"#{url}.json"
end
def transform_url(url) = "#{url}.json"
def content
data = JSON.parse(body)
@content = data.dig("files",0, "content")
end
def content = json_body.dig("files",0, "content")
def filename
data = JSON.parse(body)
data.dig("files",0, "filename")
end
def filename = json_body.dig("files",0, "filename")
end
PROVIDERS = [

View File

@@ -1,4 +1,4 @@
module Picop
module Picopackage
module Scanner
def self.scan(directory, pattern: "**/*")
Dir.glob(File.join(directory, pattern)).select do |file|

View File

@@ -1,7 +1,7 @@
require "yaml"
require "digest"
module Picop
module Picopackage
class SourceFile
attr_reader :content, :metadata, :code, :original_path
@@ -9,12 +9,15 @@ module Picop
def self.from_file(file_path) = new(content: File.read(file_path), original_path: file_path)
def self.from_content(content, filename: nil)
def self.from_content(content, metadata: {})
instance = new(content: content)
if filename && !instance.metadata['filename']
metadata = instance.metadata.merge('filename' => filename)
instance.update_metadata(metadata) #TODO: FIX THIS
end
instance.imported! if instance.metadata.empty?
updated_metadata = metadata.merge(instance.metadata)
## For new Picopackages, we should add metadata and checksum
instance.update_metadata(updated_metadata)
instance
end
@@ -26,9 +29,17 @@ module Picop
@code = extract_code
end
def imported! = @imported = true
def imported? = @imported ||= false
def content = @content
def url = @metadata['url']
def filename = @metadata['filename']
def version = @metadata['version'] || '0.0.0'
def version = @metadata['version'] || '0.0.1'
def checksum = "sha256:#{Digest::SHA256.hexdigest(code)}"
@@ -57,9 +68,9 @@ module Picop
@content = generate_content
end
def sign
def digest!
hash = checksum
return puts "File already signed" if metadata['content_checksum'] == hash
return puts "File already has a checksum" if metadata['content_checksum'] == hash
new_metadata = metadata.merge('content_checksum' => hash)
update_metadata(new_metadata)

View File

@@ -1,5 +1,5 @@
# frozen_string_literal: true
module Picop
VERSION = "0.1.0"
module Picopackage
VERSION = "0.2.0"
end

View File

@@ -8,17 +8,17 @@ Gem::Specification.new do |spec|
spec.authors = ["Dan Milne"]
spec.email = ["d@nmilne.com"]
spec.summary = "TODO: Write a short summary, because RubyGems requires one."
spec.description = "TODO: Write a longer description or delete this line."
spec.homepage = "TODO: Put your gem's website or public repo URL here."
spec.summary = "Picopackage Tool."
spec.description = "Picopackage Tool for managing Picopackages."
spec.homepage = "https://picopackage.org"
spec.license = "MIT"
spec.required_ruby_version = ">= 3.1.0"
spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
#spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
#spec.metadata["homepage_uri"] = spec.homepage
#spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
#spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.