Cross-platform Generate xlsx Document

Hmm… I downloaded the current version, but if you look in the file you’ll see it is using the old names.

Oops I don’t use Github much and apparently I did have an old version (I just downloaded the master branch). Just discovered the releases page, and that has a newer version. Sorry for my ignorance.

1 Like

NO problem, you are now smarter than before! :wink:

Actually I’m looking at the latest and somehow he has the old namespaces etc.
He may have inserted a regression by pasting from an old version ?

ADD: Which also means his change log is erroneous as to RubyZip version support.

I suggest opening an issue, or perhaps forking, fixing and do a pull request.

Looks like it was intentional.

Oh I see now. He’s ALSO using …

… to try and use old code with newer RubyZip.

zip-zip provides a simple adapter to let all your dependencies use RubyZip v1.0.0. It is very simple and light weight, aliasing the old class names to the new.

1 Like

Now the light bulb is starting to turn on!

:bulb:

I hate having all those rb files flying around. I wrote a little script to package it all into a single rb within my plugin module.

Create Package.rb
module BC 
  def self.get_requires(base_path, file_name, requires = [], level = 0)
    #return if level > 10

    file_path_name = file_name.end_with?('.rb') ? base_path + file_name : base_path + file_name + '.rb'
    return [] unless File.exist?(file_path_name)

    file = File.open(file_path_name)
    file_data = file.readlines.map(&:chomp)
    requires_found = file_data.select { |l| l.include?("require '") && l.lstrip[0] != '#' }.map { |r| r.split("'")[1] }
    file.close
    requires_found.each do |r|
      next if requires.include?(r) || r == file_name

      requires.push r
      get_requires(base_path, r, requires, level + 1)
      requires.push r
    end
    requires.push file_name if level == 0

    #filter out duplicates
    if level == 0
      rq = []
      requires.reverse.each { |r| rq.push(r) unless rq.include?(r) }
      requires = rq.reverse
    end

    requires
  end

  def self.generate_package(base_path, loader)    
    requires = get_requires(base_path, loader)
    
    #add the requires that are not referencing a file in the package
    package_lines = ["module " + name]
    package_lines += requires.reject { |f| File.exist?(base_path + f) || File.exist?(base_path + f + '.rb') }.map { |l| "require '" + l + "'" }    
    package_lines.push "\nend"

    #add the file contents from each required file.
    files = requires.select { |f| File.exist?(base_path + f) || File.exist?(base_path + f + '.rb') }
    files.each do |f|
      f_name = f.end_with?('.rb') ? base_path + f : base_path + f + '.rb'
      next unless File.exist? f_name

      file = File.open(f_name)
      file_data = file.readlines.map(&:chomp)
      #strip out requires pointing to files included in package
      #strip out comments and empty lines
      package_lines.push("module " + name)
      package_lines.push("#" + f)
      package_lines += file_data.reject { |l| l.strip[0] == '#' || l.include?("require '") || l.strip == '' }
      package_lines.push "\nend"
      file.close
    end
    #package_lines.push "\nend"

    #save the package to file
    output_file = loader.gsub('.rb', '') + '(packaged).rb'
    open(base_path + output_file, 'w') { |f| f.puts package_lines.join("\n") }
  end
end

1 Like

Yea, I remember playing with a “parrot” script similar to this.

Don’t remember if I got it working as desired. I wanted it to encapsulate a gem within another namespace, rather than producing re-namespaced gem files (as your example does.)

But, … what works works. :wink:


... a note on working with files (click to expand.)

Perhaps cleaner as:

    file_data = IO::readlines(file_path_name).map(&:chomp)
    requires_found = file_data.select { |l|
      l.include?("require '") && l.lstrip[0] != '#' }.map { |r| r.split("'")[1]
    }

But it you really prefer the #readlines instance method inherited from superclass IO, then seek to leverage the block form of File::open as often as you can. It automatically closes the file object, and returns the result of the block.

    file_data = File::open(file_path_name) do |file|
      file.readlines.map(&:chomp)
    end
    requires_found = file_data.select { |l|
      l.include?("require '") && l.lstrip[0] != '#' }.map { |r| r.split("'")[1]
    }

Also do as little as needed within the block, and save other processing for afterward outside the block, so the file stream is not left open longer than necessary.

1 Like

Interesting script.
What I usually do is combine all separate .rb files into 1 big file and then add 2 modules at the top to serve as a namespace. Combining the files into 1 big file also speeds up the loading process significally.

Sorry, did not have access to my pc in the weekend. But, I see you have it all sorted out.

1 Like

… by reducing the entries in the global $LOADED_FEATURES array, which will serve to speed up searches (using string comparisons on the array members) for the require method.

1 Like

Don’t you have to strip out the requires, and make sure the files are loaded in the right order?

What is the difference? Can you shed some :bulb: on my ignorance. I noticed I had to prefix the class with my namespace even when initializing the class from within my module, but i wasn’t sure why. (I’m afraid I’m revealing my ignorance :grimacing:)

Yes. But that can be automated by creating a dependency tree and flattening the files into 1. You just start with the first file and iterate all require and require_relative and remember which absolute paths have been parsed. if the current require is requiring a new file you grab the contents and replace the require with the contents. If the require is pointing to a file that has already been loaded you just comment out that line. Now, if you do this recursively, you will end up with a large file, containing all sources in the correct order.

1 Like

You make it sound so simple :wink: That is basically what my script does.

Do you have a similar script for packaging html/js/css into constants inside a main ruby file? Then the whole plugin could exist in a single rb except for the image assets.

Yea, as I remember, it was exactly what Ken describes that I had problems with.

The difference would be that you are creating a snapshot using a particular gem’s version for your extension. This works because you test and know that version works with your extension and the version of Ruby that SketchUp uses at that time.
This might change in the future as newer version of Ruby are distro’d with SketchUp.

My idea was a more “on the fly” approach to get around some of the gem install issues. Where an extension could request a certain gem version, and it would be encapsulated when the command was run.
I didn’t fully finish it, It was still in the try and fail stage.

I would once having the new gem code text, use Module.module_eval and pass it the text

html_text = File.read(File.join(__dir__,"some_html_file.html"))

… and then …

html = %[#{html_text}]

… or …

HTML ||= %[#{html_text}]

You can also use #{} string interpolation within HEREDOCs.

I see, except for the combining into 1 file part. But, this should work as well the way you do it.
Now, you will notice in write_xlsx that there are a few references to modules and classes where there is pointing to a global module while -because you subnamespaced it- actually a relative module is needed. I do not really know how to explain this in English, but, here we go: in write_xlsx you will see a lot of references that start with ::, for example ‘::Zip’, thus, pointing to a toplevel class or module named Zip. But, since you have subnamespaced Zip as, for example Neil::Zip, ::Zip will give an error.

Yes and no. My GUI’s are react.js apps, so they are kind of packaged. But, I do not package them into a .rb file. The only reason I could see is to hide the sources? But then… Developer Console is there for anybody to see the sources.